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 "CSSParser.h"
31 #include "CSSPropertyNames.h"
32 #include "CSSValueKeywords.h"
33 #include "CSSValuePool.h"
35 #include "EditingStyle.h"
38 #include "HTMLFontElement.h"
39 #include "HTMLInterchange.h"
40 #include "HTMLNames.h"
42 #include "NodeTraversal.h"
44 #include "RenderObject.h"
45 #include "RenderText.h"
46 #include "StylePropertySet.h"
47 #include "StyleResolver.h"
49 #include "TextIterator.h"
50 #include "VisibleUnits.h"
51 #include "htmlediting.h"
52 #include <wtf/StdLibExtras.h>
53 #include <wtf/text/StringBuilder.h>
57 using namespace HTMLNames;
59 static String& styleSpanClassString()
61 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
62 return styleSpanClassString;
65 bool isLegacyAppleStyleSpan(const Node *node)
67 if (!node || !node->isHTMLElement())
70 const HTMLElement* elem = toHTMLElement(node);
71 return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
74 static bool hasNoAttributeOrOnlyStyleAttribute(const StyledElement* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
76 if (!element->hasAttributes())
79 unsigned matchedAttributes = 0;
80 if (element->getAttribute(classAttr) == styleSpanClassString())
82 if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute
83 || !element->inlineStyle() || element->inlineStyle()->isEmpty()))
86 ASSERT(matchedAttributes <= element->attributeCount());
87 return matchedAttributes == element->attributeCount();
90 bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element)
92 if (!element || !element->hasTagName(spanTag))
94 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute);
97 static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node)
99 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
101 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(node), StyleAttributeShouldBeEmpty);
104 bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
106 if (!element || !element->hasTagName(fontTag))
109 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty);
112 static PassRefPtr<Element> createFontElement(Document* document)
114 RefPtr<Element> fontNode = createHTMLElement(document, fontTag);
115 return fontNode.release();
118 PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
120 RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag);
121 return styleElement.release();
124 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel)
125 : CompositeEditCommand(document)
126 , m_style(style->copy())
127 , m_editingAction(editingAction)
128 , m_propertyLevel(propertyLevel)
129 , m_start(endingSelection().start().downstream())
130 , m_end(endingSelection().end().upstream())
131 , m_useEndingSelection(true)
132 , m_styledInlineElement(0)
133 , m_removeOnly(false)
134 , m_isInlineElementToRemoveFunction(0)
138 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
139 : CompositeEditCommand(document)
140 , m_style(style->copy())
141 , m_editingAction(editingAction)
142 , m_propertyLevel(propertyLevel)
145 , m_useEndingSelection(false)
146 , m_styledInlineElement(0)
147 , m_removeOnly(false)
148 , m_isInlineElementToRemoveFunction(0)
152 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction)
153 : CompositeEditCommand(element->document())
154 , m_style(EditingStyle::create())
155 , m_editingAction(editingAction)
156 , m_propertyLevel(PropertyDefault)
157 , m_start(endingSelection().start().downstream())
158 , m_end(endingSelection().end().upstream())
159 , m_useEndingSelection(true)
160 , m_styledInlineElement(element)
161 , m_removeOnly(removeOnly)
162 , m_isInlineElementToRemoveFunction(0)
166 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction)
167 : CompositeEditCommand(document)
168 , m_style(style->copy())
169 , m_editingAction(editingAction)
170 , m_propertyLevel(PropertyDefault)
171 , m_start(endingSelection().start().downstream())
172 , m_end(endingSelection().end().upstream())
173 , m_useEndingSelection(true)
174 , m_styledInlineElement(0)
176 , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction)
180 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
182 ASSERT(comparePositions(newEnd, newStart) >= 0);
184 if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
185 m_useEndingSelection = true;
187 bool wasBaseFirst = startingSelection().isBaseFirst() || !startingSelection().isDirectional();
188 setEndingSelection(VisibleSelection(wasBaseFirst ? newStart : newEnd, wasBaseFirst ? newEnd : newStart, VP_DEFAULT_AFFINITY, endingSelection().isDirectional()));
193 Position ApplyStyleCommand::startPosition()
195 if (m_useEndingSelection)
196 return endingSelection().start();
201 Position ApplyStyleCommand::endPosition()
203 if (m_useEndingSelection)
204 return endingSelection().end();
209 void ApplyStyleCommand::doApply()
211 switch (m_propertyLevel) {
212 case PropertyDefault: {
213 // Apply the block-centric properties of the style.
214 RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties();
215 if (!blockStyle->isEmpty())
216 applyBlockStyle(blockStyle.get());
217 // Apply any remaining styles to the inline elements.
218 if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) {
219 applyRelativeFontStyleChange(m_style.get());
220 applyInlineStyle(m_style.get());
224 case ForceBlockProperties:
225 // Force all properties to be applied as block styles.
226 applyBlockStyle(m_style.get());
231 EditAction ApplyStyleCommand::editingAction() const
233 return m_editingAction;
236 void ApplyStyleCommand::applyBlockStyle(EditingStyle *style)
238 // update document layout once before removing styles
239 // so that we avoid the expense of updating before each and every call
240 // to check a computed style
241 document()->updateLayoutIgnorePendingStylesheets();
243 // get positions we want to use for applying style
244 Position start = startPosition();
245 Position end = endPosition();
246 if (comparePositions(end, start) < 0) {
247 Position swap = start;
252 VisiblePosition visibleStart(start);
253 VisiblePosition visibleEnd(end);
255 if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
258 // Save and restore the selection endpoints using their indices in the document, since
259 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
260 // Calculate start and end indices from the start of the tree that they're in.
261 Node* scope = highestAncestor(visibleStart.deepEquivalent().deprecatedNode());
262 RefPtr<Range> startRange = Range::create(document(), firstPositionInNode(scope), visibleStart.deepEquivalent().parentAnchoredEquivalent());
263 RefPtr<Range> endRange = Range::create(document(), firstPositionInNode(scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
264 int startIndex = TextIterator::rangeLength(startRange.get(), true);
265 int endIndex = TextIterator::rangeLength(endRange.get(), true);
267 VisiblePosition paragraphStart(startOfParagraph(visibleStart));
268 VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
269 VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
270 while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
271 StyleChange styleChange(style, paragraphStart.deepEquivalent());
272 if (styleChange.cssStyle().length() || m_removeOnly) {
273 RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode());
275 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
279 ASSERT(!block || block->isHTMLElement());
280 if (block && block->isHTMLElement()) {
281 removeCSSStyle(style, toHTMLElement(block.get()));
283 addBlockStyle(styleChange, toHTMLElement(block.get()));
286 if (nextParagraphStart.isOrphan())
287 nextParagraphStart = endOfParagraph(paragraphStart).next();
290 paragraphStart = nextParagraphStart;
291 nextParagraphStart = endOfParagraph(paragraphStart).next();
294 startRange = TextIterator::rangeFromLocationAndLength(toContainerNode(scope), startIndex, 0, true);
295 endRange = TextIterator::rangeFromLocationAndLength(toContainerNode(scope), endIndex, 0, true);
296 if (startRange && endRange)
297 updateStartEnd(startRange->startPosition(), endRange->startPosition());
300 static PassRefPtr<MutableStylePropertySet> copyStyleOrCreateEmpty(const StylePropertySet* style)
303 return MutableStylePropertySet::create();
304 return style->mutableCopy();
307 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style)
309 static const float MinimumFontSize = 0.1f;
311 if (!style || !style->hasFontSizeDelta())
314 Position start = startPosition();
315 Position end = endPosition();
316 if (comparePositions(end, start) < 0) {
317 Position swap = start;
322 // Join up any adjacent text nodes.
323 if (start.deprecatedNode()->isTextNode()) {
324 joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end);
325 start = startPosition();
329 if (start.isNull() || end.isNull())
332 if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) {
333 joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end);
334 start = startPosition();
338 if (start.isNull() || end.isNull())
341 // Split the start text nodes if needed to apply style.
342 if (isValidCaretPositionInTextNode(start)) {
343 splitTextAtStart(start, end);
344 start = startPosition();
348 if (isValidCaretPositionInTextNode(end)) {
349 splitTextAtEnd(start, end);
350 start = startPosition();
354 // Calculate loop end point.
355 // If the end node is before the start node (can only happen if the end node is
356 // an ancestor of the start node), we gather nodes up to the next sibling of the end node
358 if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode()))
359 beyondEnd = NodeTraversal::nextSkippingChildren(end.deprecatedNode());
361 beyondEnd = NodeTraversal::next(end.deprecatedNode());
363 start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
364 Node* startNode = start.deprecatedNode();
365 if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
366 startNode = NodeTraversal::next(startNode);
368 // Store away font size before making any changes to the document.
369 // This ensures that changes to one node won't effect another.
370 HashMap<Node*, float> startingFontSizes;
371 for (Node *node = startNode; node != beyondEnd; node = NodeTraversal::next(node))
372 startingFontSizes.set(node, computedFontSize(node));
374 // These spans were added by us. If empty after font size changes, they can be removed.
375 Vector<RefPtr<HTMLElement> > unstyledSpans;
377 Node* lastStyledNode = 0;
378 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(node)) {
379 RefPtr<HTMLElement> element;
380 if (node->isHTMLElement()) {
381 // Only work on fully selected nodes.
382 if (!nodeFullySelected(node, start, end))
384 element = toHTMLElement(node);
385 } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
386 // Last styled node was not parent node of this text node, but we wish to style this
387 // text node. To make this possible, add a style span to surround this text node.
388 RefPtr<HTMLElement> span = createStyleSpanElement(document());
389 surroundNodeRangeWithElement(node, node, span.get());
390 element = span.release();
392 // Only handle HTML elements and text nodes.
395 lastStyledNode = node;
397 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
398 float currentFontSize = computedFontSize(node);
399 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta());
400 RefPtr<CSSValue> value = inlineStyle->getPropertyCSSValue(CSSPropertyFontSize);
402 element->removeInlineStyleProperty(CSSPropertyFontSize);
403 currentFontSize = computedFontSize(node);
405 if (currentFontSize != desiredFontSize) {
406 inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false);
407 setNodeAttribute(element.get(), styleAttr, inlineStyle->asText());
409 if (inlineStyle->isEmpty()) {
410 removeNodeAttribute(element.get(), styleAttr);
411 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get()))
412 unstyledSpans.append(element.release());
416 size_t size = unstyledSpans.size();
417 for (size_t i = 0; i < size; ++i)
418 removeNodePreservingChildren(unstyledSpans[i].get());
421 static Node* dummySpanAncestorForNode(const Node* node)
423 while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node))))
424 node = node->parentNode();
426 return node ? node->parentNode() : 0;
429 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
431 if (!dummySpanAncestor)
434 // Dummy spans are created when text node is split, so that style information
435 // can be propagated, which can result in more splitting. If a dummy span gets
436 // cloned/split, the new node is always a sibling of it. Therefore, we scan
437 // all the children of the dummy's parent
439 for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
440 next = node->nextSibling();
441 if (isSpanWithoutAttributesOrUnstyledStyleSpan(node))
442 removeNodePreservingChildren(node);
447 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection)
449 // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
450 // In that case, we return the unsplit ancestor. Otherwise, we return 0.
451 Node* block = enclosingBlock(node);
455 Node* highestAncestorWithUnicodeBidi = 0;
456 Node* nextHighestAncestorWithUnicodeBidi = 0;
457 int highestAncestorUnicodeBidi = 0;
458 for (Node* n = node->parentNode(); n != block; n = n->parentNode()) {
459 int unicodeBidi = getIdentifierValue(n, CSSPropertyUnicodeBidi);
460 if (unicodeBidi && unicodeBidi != CSSValueNormal) {
461 highestAncestorUnicodeBidi = unicodeBidi;
462 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
463 highestAncestorWithUnicodeBidi = n;
467 if (!highestAncestorWithUnicodeBidi)
470 HTMLElement* unsplitAncestor = 0;
472 WritingDirection highestAncestorDirection;
473 if (allowedDirection != NaturalWritingDirection
474 && highestAncestorUnicodeBidi != CSSValueBidiOverride
475 && highestAncestorWithUnicodeBidi->isHTMLElement()
476 && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection)
477 && highestAncestorDirection == allowedDirection) {
478 if (!nextHighestAncestorWithUnicodeBidi)
479 return toHTMLElement(highestAncestorWithUnicodeBidi);
481 unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi);
482 highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
485 // Split every ancestor through highest ancestor with embedding.
486 RefPtr<Node> currentNode = node;
487 while (currentNode) {
488 RefPtr<Element> parent = toElement(currentNode->parentNode());
489 if (before ? currentNode->previousSibling() : currentNode->nextSibling())
490 splitElement(parent, before ? currentNode : currentNode->nextSibling());
491 if (parent == highestAncestorWithUnicodeBidi)
493 currentNode = parent;
495 return unsplitAncestor;
498 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
500 Node* block = enclosingBlock(node);
505 for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = parent) {
506 parent = n->parentNode();
507 if (!n->isStyledElement())
510 StyledElement* element = static_cast<StyledElement*>(n);
511 int unicodeBidi = getIdentifierValue(element, CSSPropertyUnicodeBidi);
512 if (!unicodeBidi || unicodeBidi == CSSValueNormal)
515 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
516 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
517 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
518 // otherwise it sets the property in the inline style declaration.
519 if (element->hasAttribute(dirAttr)) {
520 // FIXME: If this is a BDO element, we should probably just remove it if it has no
521 // other attributes, like we (should) do with B and I elements.
522 removeNodeAttribute(element, dirAttr);
524 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
525 inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
526 inlineStyle->removeProperty(CSSPropertyDirection);
527 setNodeAttribute(element, styleAttr, inlineStyle->asText());
528 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
529 removeNodePreservingChildren(element);
534 static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode)
536 for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) {
537 if (n->isHTMLElement() && getIdentifierValue(n, CSSPropertyUnicodeBidi) == CSSValueEmbed)
544 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style)
546 RefPtr<Node> startDummySpanAncestor = 0;
547 RefPtr<Node> endDummySpanAncestor = 0;
549 // update document layout once before removing styles
550 // so that we avoid the expense of updating before each and every call
551 // to check a computed style
552 document()->updateLayoutIgnorePendingStylesheets();
554 // adjust to the positions we want to use for applying style
555 Position start = startPosition();
556 Position end = endPosition();
558 if (start.isNull() || end.isNull())
561 if (comparePositions(end, start) < 0) {
562 Position swap = start;
567 // split the start node and containing element if the selection starts inside of it
568 bool splitStart = isValidCaretPositionInTextNode(start);
570 if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style))
571 splitTextElementAtStart(start, end);
573 splitTextAtStart(start, end);
574 start = startPosition();
576 startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode());
579 // split the end node and containing element if the selection ends inside of it
580 bool splitEnd = isValidCaretPositionInTextNode(end);
582 if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style))
583 splitTextElementAtEnd(start, end);
585 splitTextAtEnd(start, end);
586 start = startPosition();
588 endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode());
591 // Remove style from the selection.
592 // Use the upstream position of the start for removing style.
593 // This will ensure we remove all traces of the relevant styles from the selection
594 // and prevent us from adding redundant ones, as described in:
595 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
596 Position removeStart = start.upstream();
597 WritingDirection textDirection = NaturalWritingDirection;
598 bool hasTextDirection = style->textDirection(textDirection);
599 RefPtr<EditingStyle> styleWithoutEmbedding;
600 RefPtr<EditingStyle> embeddingStyle;
601 if (hasTextDirection) {
602 // Leave alone an ancestor that provides the desired single level embedding, if there is one.
603 HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection);
604 HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection);
605 removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor);
606 removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor);
608 // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
609 Position embeddingRemoveStart = removeStart;
610 if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
611 embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
613 Position embeddingRemoveEnd = end;
614 if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
615 embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
617 if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
618 styleWithoutEmbedding = style->copy();
619 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
621 if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
622 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd);
626 removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end);
627 start = startPosition();
629 if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan())
632 if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) {
633 start = startPosition();
638 mergeEndWithNextIfIdentical(start, end);
639 start = startPosition();
643 // update document layout once before running the rest of the function
644 // so that we avoid the expense of updating before each and every call
645 // to check a computed style
646 document()->updateLayoutIgnorePendingStylesheets();
648 RefPtr<EditingStyle> styleToApply = style;
649 if (hasTextDirection) {
650 // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
651 Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode()));
652 Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode()));
654 if (embeddingStartNode || embeddingEndNode) {
655 Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(embeddingStartNode) : start;
656 Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end;
657 ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull());
659 if (!embeddingStyle) {
660 styleWithoutEmbedding = style->copy();
661 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
663 fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
665 styleToApply = styleWithoutEmbedding;
669 fixRangeAndApplyInlineStyle(styleToApply.get(), start, end);
671 // Remove dummy style spans created by splitting text elements.
672 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor.get());
673 if (endDummySpanAncestor != startDummySpanAncestor)
674 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor.get());
677 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end)
679 Node* startNode = start.deprecatedNode();
681 if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) {
682 startNode = NodeTraversal::next(startNode);
683 if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0)
687 Node* pastEndNode = end.deprecatedNode();
688 if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode()))
689 pastEndNode = NodeTraversal::nextSkippingChildren(end.deprecatedNode());
691 // FIXME: Callers should perform this operation on a Range that includes the br
692 // if they want style applied to the empty line.
693 if (start == end && start.deprecatedNode()->hasTagName(brTag))
694 pastEndNode = NodeTraversal::next(start.deprecatedNode());
696 // Start from the highest fully selected ancestor so that we can modify the fully selected node.
697 // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run
698 // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font>
699 RefPtr<Range> range = Range::create(startNode->document(), start, end);
700 Element* editableRoot = startNode->rootEditableElement();
701 if (startNode != editableRoot) {
702 while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get()))
703 startNode = startNode->parentNode();
706 applyInlineStyleToNodeRange(style, startNode, pastEndNode);
709 static bool containsNonEditableRegion(Node* node)
711 if (!node->rendererIsEditable())
714 Node* sibling = NodeTraversal::nextSkippingChildren(node);
715 for (Node* descendent = node->firstChild(); descendent && descendent != sibling; descendent = NodeTraversal::next(descendent)) {
716 if (!descendent->rendererIsEditable())
723 struct InlineRunToApplyStyle {
724 InlineRunToApplyStyle(Node* start, Node* end, Node* pastEndNode)
727 , pastEndNode(pastEndNode)
729 ASSERT(start->parentNode() == end->parentNode());
732 bool startAndEndAreStillInDocument()
734 return start && end && start->inDocument() && end->inDocument();
739 RefPtr<Node> pastEndNode;
740 Position positionForStyleComputation;
741 RefPtr<Node> dummyElement;
745 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRefPtr<Node> startNode, PassRefPtr<Node> pastEndNode)
750 document()->updateLayoutIgnorePendingStylesheets();
752 Vector<InlineRunToApplyStyle> runs;
753 RefPtr<Node> node = startNode;
754 for (RefPtr<Node> next; node && node != pastEndNode; node = next) {
755 next = NodeTraversal::next(node.get());
757 if (!node->renderer() || !node->rendererIsEditable())
760 if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) {
761 // This is a plaintext-only region. Only proceed if it's fully selected.
762 // pastEndNode is the node after the last fully selected node, so if it's inside node then
763 // node isn't fully selected.
764 if (pastEndNode && pastEndNode->isDescendantOf(node.get()))
766 // Add to this element's inline style and skip over its contents.
767 HTMLElement* element = toHTMLElement(node.get());
768 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
769 inlineStyle->mergeAndOverrideOnConflict(style->style());
770 setNodeAttribute(element, styleAttr, inlineStyle->asText());
771 next = NodeTraversal::nextSkippingChildren(node.get());
775 if (isBlock(node.get()))
778 if (node->childNodeCount()) {
779 if (node->contains(pastEndNode.get()) || containsNonEditableRegion(node.get()) || !node->parentNode()->rendererIsEditable())
781 if (editingIgnoresContent(node.get())) {
782 next = NodeTraversal::nextSkippingChildren(node.get());
787 Node* runStart = node.get();
788 Node* runEnd = node.get();
789 Node* sibling = node->nextSibling();
790 while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get())
791 && (!isBlock(sibling) || sibling->hasTagName(brTag))
792 && !containsNonEditableRegion(sibling)) {
794 sibling = runEnd->nextSibling();
796 next = NodeTraversal::nextSkippingChildren(runEnd);
798 Node* pastEndNode = NodeTraversal::nextSkippingChildren(runEnd);
799 if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode))
802 runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode));
805 for (size_t i = 0; i < runs.size(); i++) {
806 removeConflictingInlineStyleFromRun(style, runs[i].start, runs[i].end, runs[i].pastEndNode);
807 runs[i].positionForStyleComputation = positionToComputeInlineStyleChange(runs[i].start, runs[i].dummyElement);
810 document()->updateLayoutIgnorePendingStylesheets();
812 for (size_t i = 0; i < runs.size(); i++)
813 runs[i].change = StyleChange(style, runs[i].positionForStyleComputation);
815 for (size_t i = 0; i < runs.size(); i++) {
816 InlineRunToApplyStyle& run = runs[i];
817 if (run.dummyElement)
818 removeNode(run.dummyElement);
819 if (run.startAndEndAreStillInDocument())
820 applyInlineStyleChange(run.start.release(), run.end.release(), run.change, AddStyledElement);
824 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
826 return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName()))
827 || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element));
830 bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* runStart, Node* pastEndNode)
832 ASSERT(style && runStart);
834 for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(node)) {
835 if (node->childNodeCount())
837 // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified
838 if (!style->styleIsPresentInComputedStyleOfNode(node))
840 if (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))
846 void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd, PassRefPtr<Node> pastEndNode)
848 ASSERT(runStart && runEnd);
849 RefPtr<Node> next = runStart;
850 for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) {
851 if (editingIgnoresContent(node.get())) {
852 ASSERT(!node->contains(pastEndNode.get()));
853 next = NodeTraversal::nextSkippingChildren(node.get());
855 next = NodeTraversal::next(node.get());
856 if (!node->isHTMLElement())
859 RefPtr<Node> previousSibling = node->previousSibling();
860 RefPtr<Node> nextSibling = node->nextSibling();
861 RefPtr<ContainerNode> parent = node->parentNode();
862 removeInlineStyleFromElement(style, toHTMLElement(node.get()), RemoveAlways);
863 if (!node->inDocument()) {
864 // FIXME: We might need to update the start and the end of current selection here but need a test.
865 if (runStart == node)
866 runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild();
868 runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild();
873 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
877 if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
880 if (isStyledInlineElementToRemove(element.get())) {
881 if (mode == RemoveNone)
883 ASSERT(extractedStyle);
884 extractedStyle->mergeInlineStyleOfElement(element.get(), EditingStyle::OverrideValues);
885 removeNodePreservingChildren(element);
889 bool removed = false;
890 if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle))
893 if (!element->inDocument())
896 // If the node was converted to a span, the span may still contain relevant
897 // styles which must be removed (e.g. <b style='font-weight: bold'>)
898 if (removeCSSStyle(style, element.get(), mode, extractedStyle))
904 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
906 if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty))
907 removeNodePreservingChildren(elem);
909 HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(elem);
910 ASSERT(newSpanElement && newSpanElement->inDocument());
911 elem = newSpanElement;
915 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
918 if (mode == RemoveNone) {
919 ASSERT(!extractedStyle);
920 return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element);
923 ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways);
924 if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) {
925 replaceWithSpanOrRemoveIfWithoutAttributes(element);
929 // unicode-bidi and direction are pushed down separately so don't push down with other styles
930 Vector<QualifiedName> attributes;
931 if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection,
932 extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle))
935 for (size_t i = 0; i < attributes.size(); i++)
936 removeNodeAttribute(element, attributes[i]);
938 if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element))
939 removeNodePreservingChildren(element);
944 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
949 if (mode == RemoveNone)
950 return style->conflictsWithInlineStyleOfElement(element);
952 Vector<CSSPropertyID> properties;
953 if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties))
956 // FIXME: We should use a mass-removal function here but we don't have an undoable one yet.
957 for (size_t i = 0; i < properties.size(); i++)
958 removeCSSProperty(element, properties[i]);
960 // No need to serialize <foo style=""> if we just removed the last css property
961 if (element->inlineStyle()->isEmpty())
962 removeNodeAttribute(element, styleAttr);
964 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
965 removeNodePreservingChildren(element);
970 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node)
975 HTMLElement* result = 0;
976 Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node));
978 for (Node *n = node; n; n = n->parentNode()) {
979 if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n)))
980 result = toHTMLElement(n);
981 // Should stop at the editable root (cannot cross editing boundary) and
982 // also stop at the unsplittable element to be consistent with other UAs
983 if (n == unsplittableElement)
990 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style)
994 node->document()->updateStyleIfNeeded();
996 if (!style || style->isEmpty() || !node->renderer())
999 RefPtr<EditingStyle> newInlineStyle = style;
1000 if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) {
1001 newInlineStyle = style->copy();
1002 newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues);
1005 // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
1006 // FIXME: applyInlineStyleToRange should be used here instead.
1007 if ((node->renderer()->isBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) {
1008 setNodeAttribute(toHTMLElement(node), styleAttr, newInlineStyle->style()->asText());
1012 if (node->renderer()->isText() && static_cast<RenderText*>(node->renderer())->isAllCollapsibleWhitespace())
1015 // We can't wrap node with the styled element here because new styled element will never be removed if we did.
1016 // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element
1017 // then we fall into an infinite loop where we keep removing and adding styled element wrapping node.
1018 addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement);
1021 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode)
1023 HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode);
1024 if (!highestAncestor)
1027 // The outer loop is traversing the tree vertically from highestAncestor to targetNode
1028 RefPtr<Node> current = highestAncestor;
1029 // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown.
1030 // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
1031 Vector<RefPtr<Element> > elementsToPushDown;
1032 while (current && current != targetNode && current->contains(targetNode)) {
1033 NodeVector currentChildren;
1034 getChildNodes(current.get(), currentChildren);
1035 RefPtr<StyledElement> styledElement;
1036 if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current.get()))) {
1037 styledElement = static_cast<StyledElement*>(current.get());
1038 elementsToPushDown.append(styledElement);
1041 RefPtr<EditingStyle> styleToPushDown = EditingStyle::create();
1042 if (current->isHTMLElement())
1043 removeInlineStyleFromElement(style, toHTMLElement(current.get()), RemoveIfNeeded, styleToPushDown.get());
1045 // The inner loop will go through children on each level
1046 // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
1047 for (size_t i = 0; i < currentChildren.size(); ++i) {
1048 Node* child = currentChildren[i].get();
1049 if (!child->parentNode())
1051 if (!child->contains(targetNode) && elementsToPushDown.size()) {
1052 for (size_t i = 0; i < elementsToPushDown.size(); i++) {
1053 RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren();
1054 wrapper->removeAttribute(styleAttr);
1055 surroundNodeRangeWithElement(child, child, wrapper);
1059 // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode
1060 // But if we've removed styledElement then go ahead and always apply the style.
1061 if (child != targetNode || styledElement)
1062 applyInlineStyleToPushDown(child, styleToPushDown.get());
1064 // We found the next node for the outer loop (contains targetNode)
1065 // When reached targetNode, stop the outer loop upon the completion of the current inner loop
1066 if (child == targetNode || child->contains(targetNode))
1072 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end)
1074 ASSERT(start.isNotNull());
1075 ASSERT(end.isNotNull());
1076 ASSERT(start.anchorNode()->inDocument());
1077 ASSERT(end.anchorNode()->inDocument());
1078 ASSERT(comparePositions(start, end) <= 0);
1079 // FIXME: We should assert that start/end are not in the middle of a text node.
1081 Position pushDownStart = start.downstream();
1082 // If the pushDownStart is at the end of a text node, then this node is not fully selected.
1083 // Move it to the next deep quivalent position to avoid removing the style from this node.
1084 // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1085 Node* pushDownStartContainer = pushDownStart.containerNode();
1086 if (pushDownStartContainer && pushDownStartContainer->isTextNode()
1087 && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset())
1088 pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
1089 // If pushDownEnd is at the start of a text node, then this node is not fully selected.
1090 // Move it to the previous deep equivalent position to avoid removing the style from this node.
1091 Position pushDownEnd = end.upstream();
1092 Node* pushDownEndContainer = pushDownEnd.containerNode();
1093 if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownEnd.computeOffsetInContainerNode())
1094 pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd);
1096 pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode());
1097 pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode());
1099 // The s and e variables store the positions used to set the ending selection after style removal
1100 // takes place. This will help callers to recognize when either the start node or the end node
1101 // are removed from the document during the work of this function.
1102 // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(),
1103 // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune.
1104 Position s = start.isNull() || start.isOrphan() ? pushDownStart : start;
1105 Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end;
1107 Node* node = start.deprecatedNode();
1110 if (editingIgnoresContent(node)) {
1111 ASSERT(node == end.deprecatedNode() || !node->contains(end.deprecatedNode()));
1112 next = NodeTraversal::nextSkippingChildren(node);
1114 next = NodeTraversal::next(node);
1115 if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
1116 RefPtr<HTMLElement> elem = toHTMLElement(node);
1117 RefPtr<Node> prev = NodeTraversal::previousPostOrder(elem.get());
1118 RefPtr<Node> next = NodeTraversal::next(elem.get());
1119 RefPtr<EditingStyle> styleToPushDown;
1120 RefPtr<Node> childNode;
1121 if (isStyledInlineElementToRemove(elem.get())) {
1122 styleToPushDown = EditingStyle::create();
1123 childNode = elem->firstChild();
1126 removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get());
1127 if (!elem->inDocument()) {
1128 if (s.deprecatedNode() == elem) {
1129 // Since elem must have been fully selected, and it is at the start
1130 // of the selection, it is clear we can set the new s offset to 0.
1131 ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0);
1132 s = firstPositionInOrBeforeNode(next.get());
1134 if (e.deprecatedNode() == elem) {
1135 // Since elem must have been fully selected, and it is at the end
1136 // of the selection, it is clear we can set the new e offset to
1137 // the max range offset of prev.
1138 ASSERT(s.anchorType() == Position::PositionIsAfterAnchor || !offsetIsBeforeLastNodeOffset(s.offsetInContainerNode(), s.containerNode()));
1139 e = lastPositionInOrAfterNode(prev.get());
1143 if (styleToPushDown) {
1144 for (; childNode; childNode = childNode->nextSibling())
1145 applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get());
1148 if (node == end.deprecatedNode())
1153 updateStartEnd(s, e);
1156 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
1159 ASSERT(node->isElementNode());
1161 // The tree may have changed and Position::upstream() relies on an up-to-date layout.
1162 node->document()->updateLayoutIgnorePendingStylesheets();
1164 return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0
1165 && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0;
1168 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
1171 ASSERT(node->isElementNode());
1173 bool isFullyBeforeStart = comparePositions(lastPositionInOrAfterNode(node).upstream(), start) < 0;
1174 bool isFullyAfterEnd = comparePositions(firstPositionInOrBeforeNode(node), end) > 0;
1176 return isFullyBeforeStart || isFullyAfterEnd;
1179 void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end)
1181 ASSERT(start.containerNode()->isTextNode());
1184 if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode())
1185 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
1189 RefPtr<Text> text = start.containerText();
1190 splitTextNode(text, start.offsetInContainerNode());
1191 updateStartEnd(firstPositionInNode(text.get()), newEnd);
1194 void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end)
1196 ASSERT(end.containerNode()->isTextNode());
1198 bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode();
1199 Text* text = toText(end.deprecatedNode());
1200 splitTextNode(text, end.offsetInContainerNode());
1202 Node* prevNode = text->previousSibling();
1203 if (!prevNode || !prevNode->isTextNode())
1206 Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.offsetInContainerNode()) : start;
1207 updateStartEnd(newStart, lastPositionInNode(prevNode));
1210 void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end)
1212 ASSERT(start.containerNode()->isTextNode());
1215 if (start.containerNode() == end.containerNode())
1216 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
1220 splitTextNodeContainingElement(start.containerText(), start.offsetInContainerNode());
1221 updateStartEnd(positionBeforeNode(start.containerNode()), newEnd);
1224 void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end)
1226 ASSERT(end.containerNode()->isTextNode());
1228 bool shouldUpdateStart = start.containerNode() == end.containerNode();
1229 splitTextNodeContainingElement(end.containerText(), end.offsetInContainerNode());
1231 Node* parentElement = end.containerNode()->parentNode();
1232 if (!parentElement || !parentElement->previousSibling())
1234 Node* firstTextNode = parentElement->previousSibling()->lastChild();
1235 if (!firstTextNode || !firstTextNode->isTextNode())
1238 Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), start.offsetInContainerNode()) : start;
1239 updateStartEnd(newStart, positionAfterNode(firstTextNode));
1242 bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style)
1244 if (!element || !element->isHTMLElement())
1247 return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element));
1250 bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position)
1252 Node* node = position.containerNode();
1253 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode())
1255 int offsetInText = position.offsetInContainerNode();
1256 return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node);
1259 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end)
1261 Node* startNode = start.containerNode();
1262 int startOffset = start.computeOffsetInContainerNode();
1266 if (isAtomicNode(startNode)) {
1267 // note: prior siblings could be unrendered elements. it's silly to miss the
1268 // merge opportunity just for that.
1269 if (startNode->previousSibling())
1272 startNode = startNode->parentNode();
1276 if (!startNode->isElementNode())
1279 Node* previousSibling = startNode->previousSibling();
1281 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1282 Element* previousElement = toElement(previousSibling);
1283 Element* element = toElement(startNode);
1284 Node* startChild = element->firstChild();
1286 mergeIdenticalElements(previousElement, element);
1288 int startOffsetAdjustment = startChild->nodeIndex();
1289 int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0;
1290 updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor),
1291 Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor));
1298 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end)
1300 Node* endNode = end.containerNode();
1301 int endOffset = end.computeOffsetInContainerNode();
1303 if (isAtomicNode(endNode)) {
1304 if (offsetIsBeforeLastNodeOffset(endOffset, endNode))
1307 unsigned parentLastOffset = end.deprecatedNode()->parentNode()->childNodeCount() - 1;
1308 if (end.deprecatedNode()->nextSibling())
1311 endNode = end.deprecatedNode()->parentNode();
1312 endOffset = parentLastOffset;
1315 if (!endNode->isElementNode() || endNode->hasTagName(brTag))
1318 Node* nextSibling = endNode->nextSibling();
1319 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1320 Element* nextElement = static_cast<Element *>(nextSibling);
1321 Element* element = static_cast<Element *>(endNode);
1322 Node* nextChild = nextElement->firstChild();
1324 mergeIdenticalElements(element, nextElement);
1326 bool shouldUpdateStart = start.containerNode() == endNode;
1327 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodeCount();
1328 updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start,
1329 Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor));
1336 void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert)
1338 ASSERT(passedStartNode);
1340 ASSERT(elementToInsert);
1341 RefPtr<Node> startNode = passedStartNode;
1342 RefPtr<Element> element = elementToInsert;
1344 insertNodeBefore(element, startNode);
1346 RefPtr<Node> node = startNode;
1348 RefPtr<Node> next = node->nextSibling();
1349 if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) {
1351 appendNode(node, element);
1353 if (node == endNode)
1358 RefPtr<Node> nextSibling = element->nextSibling();
1359 RefPtr<Node> previousSibling = element->previousSibling();
1360 if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable()
1361 && areIdenticalElements(element.get(), toElement(nextSibling.get())))
1362 mergeIdenticalElements(element.get(), toElement(nextSibling.get()));
1364 if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) {
1365 Node* mergedElement = previousSibling->nextSibling();
1366 if (mergedElement->isElementNode() && mergedElement->rendererIsEditable()
1367 && areIdenticalElements(toElement(previousSibling.get()), toElement(mergedElement)))
1368 mergeIdenticalElements(toElement(previousSibling.get()), toElement(mergedElement));
1371 // FIXME: We should probably call updateStartEnd if the start or end was in the node
1372 // range so that the endingSelection() is canonicalized. See the comments at the end of
1373 // VisibleSelection::validate().
1376 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
1378 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1383 String cssStyle = styleChange.cssStyle();
1384 StringBuilder cssText;
1385 cssText.append(cssStyle);
1386 if (const StylePropertySet* decl = block->inlineStyle()) {
1387 if (!cssStyle.isEmpty())
1388 cssText.append(' ');
1389 cssText.append(decl->asText());
1391 setNodeAttribute(block, styleAttr, cssText.toString());
1394 void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement)
1396 if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument())
1399 RefPtr<Node> start = passedStart;
1400 RefPtr<Node> dummyElement;
1401 StyleChange styleChange(style, positionToComputeInlineStyleChange(start, dummyElement));
1404 removeNode(dummyElement);
1406 applyInlineStyleChange(start, passedEnd, styleChange, addStyledElement);
1409 Position ApplyStyleCommand::positionToComputeInlineStyleChange(PassRefPtr<Node> startNode, RefPtr<Node>& dummyElement)
1411 // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run.
1412 Position positionForStyleComparison;
1413 if (!startNode->isElementNode()) {
1414 dummyElement = createStyleSpanElement(document());
1415 insertNodeAt(dummyElement, positionBeforeNode(startNode.get()));
1416 return positionBeforeNode(dummyElement.get());
1419 return firstPositionInOrBeforeNode(startNode.get());
1422 void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, StyleChange& styleChange, EAddStyledElement addStyledElement)
1424 RefPtr<Node> startNode = passedStart;
1425 RefPtr<Node> endNode = passedEnd;
1426 ASSERT(startNode->inDocument());
1427 ASSERT(endNode->inDocument());
1429 // Find appropriate font and span elements top-down.
1430 HTMLElement* fontContainer = 0;
1431 HTMLElement* styleContainer = 0;
1432 for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) {
1433 if (container->isHTMLElement() && container->hasTagName(fontTag))
1434 fontContainer = toHTMLElement(container);
1435 bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag);
1436 if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount())))
1437 styleContainer = toHTMLElement(container);
1438 if (!container->firstChild())
1440 startNode = container->firstChild();
1441 endNode = container->lastChild();
1444 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1445 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1446 if (fontContainer) {
1447 if (styleChange.applyFontColor())
1448 setNodeAttribute(fontContainer, colorAttr, styleChange.fontColor());
1449 if (styleChange.applyFontFace())
1450 setNodeAttribute(fontContainer, faceAttr, styleChange.fontFace());
1451 if (styleChange.applyFontSize())
1452 setNodeAttribute(fontContainer, sizeAttr, styleChange.fontSize());
1454 RefPtr<Element> fontElement = createFontElement(document());
1455 if (styleChange.applyFontColor())
1456 fontElement->setAttribute(colorAttr, styleChange.fontColor());
1457 if (styleChange.applyFontFace())
1458 fontElement->setAttribute(faceAttr, styleChange.fontFace());
1459 if (styleChange.applyFontSize())
1460 fontElement->setAttribute(sizeAttr, styleChange.fontSize());
1461 surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
1465 if (styleChange.cssStyle().length()) {
1466 if (styleContainer) {
1467 if (const StylePropertySet* existingStyle = styleContainer->inlineStyle()) {
1468 String existingText = existingStyle->asText();
1469 StringBuilder cssText;
1470 cssText.append(existingText);
1471 if (!existingText.isEmpty())
1472 cssText.append(' ');
1473 cssText.append(styleChange.cssStyle());
1474 setNodeAttribute(styleContainer, styleAttr, cssText.toString());
1476 setNodeAttribute(styleContainer, styleAttr, styleChange.cssStyle());
1478 RefPtr<Element> styleElement = createStyleSpanElement(document());
1479 styleElement->setAttribute(styleAttr, styleChange.cssStyle());
1480 surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
1484 if (styleChange.applyBold())
1485 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));
1487 if (styleChange.applyItalic())
1488 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));
1490 if (styleChange.applyUnderline())
1491 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));
1493 if (styleChange.applyLineThrough())
1494 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), strikeTag));
1496 if (styleChange.applySubscript())
1497 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
1498 else if (styleChange.applySuperscript())
1499 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));
1501 if (m_styledInlineElement && addStyledElement == AddStyledElement)
1502 surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
1505 float ApplyStyleCommand::computedFontSize(Node* node)
1510 RefPtr<CSSValue> value = ComputedStyleExtractor(node).propertyValue(CSSPropertyFontSize);
1511 ASSERT(value && value->isPrimitiveValue());
1512 return static_cast<CSSPrimitiveValue*>(value.get())->getFloatValue(CSSPrimitiveValue::CSS_PX);
1515 void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end)
1520 Position newStart = start;
1521 Position newEnd = end;
1523 Vector<RefPtr<Text> > textNodes;
1524 for (Node* curr = node->firstChild(); curr; curr = curr->nextSibling()) {
1525 if (!curr->isTextNode())
1528 textNodes.append(toText(curr));
1531 for (size_t i = 0; i < textNodes.size(); ++i) {
1532 Text* childText = textNodes[i].get();
1533 Node* next = childText->nextSibling();
1534 if (!next || !next->isTextNode())
1537 Text* nextText = toText(next);
1538 if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode())
1539 newStart = Position(childText, childText->length() + start.offsetInContainerNode());
1540 if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode())
1541 newEnd = Position(childText, childText->length() + end.offsetInContainerNode());
1542 String textToMove = nextText->data();
1543 insertTextIntoNode(childText, childText->length(), textToMove);
1545 // don't move child node pointer. it may want to merge with more text nodes.
1548 updateStartEnd(newStart, newEnd);