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 "core/editing/ApplyStyleCommand.h"
29 #include "CSSPropertyNames.h"
30 #include "CSSValueKeywords.h"
31 #include "HTMLNames.h"
32 #include "core/css/CSSComputedStyleDeclaration.h"
33 #include "core/css/CSSValuePool.h"
34 #include "core/css/StylePropertySet.h"
35 #include "core/dom/Document.h"
36 #include "core/dom/NodeList.h"
37 #include "core/dom/NodeTraversal.h"
38 #include "core/dom/Range.h"
39 #include "core/dom/Text.h"
40 #include "core/editing/EditingStyle.h"
41 #include "core/editing/HTMLInterchange.h"
42 #include "core/editing/PlainTextRange.h"
43 #include "core/editing/TextIterator.h"
44 #include "core/editing/VisibleUnits.h"
45 #include "core/editing/htmlediting.h"
46 #include "core/rendering/RenderObject.h"
47 #include "core/rendering/RenderText.h"
48 #include "wtf/StdLibExtras.h"
49 #include "wtf/text/StringBuilder.h"
53 using namespace HTMLNames;
55 static String& styleSpanClassString()
57 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
58 return styleSpanClassString;
61 bool isLegacyAppleStyleSpan(const Node *node)
63 if (!node || !node->isHTMLElement())
66 const HTMLElement* elem = toHTMLElement(node);
67 return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
70 static bool hasNoAttributeOrOnlyStyleAttribute(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
72 if (!element->hasAttributes())
75 unsigned matchedAttributes = 0;
76 if (element->getAttribute(classAttr) == styleSpanClassString())
78 if (element->hasAttribute(styleAttr) && (shouldStyleAttributeBeEmpty == AllowNonEmptyStyleAttribute
79 || !element->inlineStyle() || element->inlineStyle()->isEmpty()))
82 ASSERT(matchedAttributes <= element->attributeCount());
83 return matchedAttributes == element->attributeCount();
86 bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element)
88 if (!element || !element->hasTagName(spanTag))
90 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), AllowNonEmptyStyleAttribute);
93 static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node)
95 if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
97 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(node), StyleAttributeShouldBeEmpty);
100 bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldStyleAttributeBeEmpty)
102 if (!element || !element->hasTagName(fontTag))
105 return hasNoAttributeOrOnlyStyleAttribute(toHTMLElement(element), shouldStyleAttributeBeEmpty);
108 static PassRefPtr<Element> createFontElement(Document& document)
110 RefPtr<Element> fontNode = createHTMLElement(document, fontTag);
111 return fontNode.release();
114 PassRefPtr<HTMLElement> createStyleSpanElement(Document& document)
116 RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag);
117 return styleElement.release();
120 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel)
121 : CompositeEditCommand(document)
122 , m_style(style->copy())
123 , m_editingAction(editingAction)
124 , m_propertyLevel(propertyLevel)
125 , m_start(endingSelection().start().downstream())
126 , m_end(endingSelection().end().upstream())
127 , m_useEndingSelection(true)
128 , m_styledInlineElement(0)
129 , m_removeOnly(false)
130 , m_isInlineElementToRemoveFunction(0)
134 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
135 : CompositeEditCommand(document)
136 , m_style(style->copy())
137 , m_editingAction(editingAction)
138 , m_propertyLevel(propertyLevel)
141 , m_useEndingSelection(false)
142 , m_styledInlineElement(0)
143 , m_removeOnly(false)
144 , m_isInlineElementToRemoveFunction(0)
148 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction)
149 : CompositeEditCommand(element->document())
150 , m_style(EditingStyle::create())
151 , m_editingAction(editingAction)
152 , m_propertyLevel(PropertyDefault)
153 , m_start(endingSelection().start().downstream())
154 , m_end(endingSelection().end().upstream())
155 , m_useEndingSelection(true)
156 , m_styledInlineElement(element)
157 , m_removeOnly(removeOnly)
158 , m_isInlineElementToRemoveFunction(0)
162 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction)
163 : CompositeEditCommand(document)
164 , m_style(style->copy())
165 , m_editingAction(editingAction)
166 , m_propertyLevel(PropertyDefault)
167 , m_start(endingSelection().start().downstream())
168 , m_end(endingSelection().end().upstream())
169 , m_useEndingSelection(true)
170 , m_styledInlineElement(0)
172 , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction)
176 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
178 ASSERT(comparePositions(newEnd, newStart) >= 0);
180 if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
181 m_useEndingSelection = true;
183 setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY, endingSelection().isDirectional()));
188 Position ApplyStyleCommand::startPosition()
190 if (m_useEndingSelection)
191 return endingSelection().start();
196 Position ApplyStyleCommand::endPosition()
198 if (m_useEndingSelection)
199 return endingSelection().end();
204 void ApplyStyleCommand::doApply()
206 switch (m_propertyLevel) {
207 case PropertyDefault: {
208 // Apply the block-centric properties of the style.
209 RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties();
210 if (!blockStyle->isEmpty())
211 applyBlockStyle(blockStyle.get());
212 // Apply any remaining styles to the inline elements.
213 if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) {
214 applyRelativeFontStyleChange(m_style.get());
215 applyInlineStyle(m_style.get());
219 case ForceBlockProperties:
220 // Force all properties to be applied as block styles.
221 applyBlockStyle(m_style.get());
226 EditAction ApplyStyleCommand::editingAction() const
228 return m_editingAction;
231 void ApplyStyleCommand::applyBlockStyle(EditingStyle *style)
233 // update document layout once before removing styles
234 // so that we avoid the expense of updating before each and every call
235 // to check a computed style
236 document().updateLayoutIgnorePendingStylesheets();
238 // get positions we want to use for applying style
239 Position start = startPosition();
240 Position end = endPosition();
241 if (comparePositions(end, start) < 0) {
242 Position swap = start;
247 VisiblePosition visibleStart(start);
248 VisiblePosition visibleEnd(end);
250 if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
253 // Save and restore the selection endpoints using their indices in the document, since
254 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
255 // Calculate start and end indices from the start of the tree that they're in.
256 Node* scope = visibleStart.deepEquivalent().deprecatedNode()->highestAncestor();
257 RefPtr<Range> startRange = Range::create(document(), firstPositionInNode(scope), visibleStart.deepEquivalent().parentAnchoredEquivalent());
258 RefPtr<Range> endRange = Range::create(document(), firstPositionInNode(scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
259 int startIndex = TextIterator::rangeLength(startRange.get(), true);
260 int endIndex = TextIterator::rangeLength(endRange.get(), true);
262 VisiblePosition paragraphStart(startOfParagraph(visibleStart));
263 VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
264 VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
265 while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
266 StyleChange styleChange(style, paragraphStart.deepEquivalent());
267 if (styleChange.cssStyle().length() || m_removeOnly) {
268 RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode());
270 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
274 ASSERT(!block || block->isHTMLElement());
275 if (block && block->isHTMLElement()) {
276 removeCSSStyle(style, toHTMLElement(block));
278 addBlockStyle(styleChange, toHTMLElement(block));
281 if (nextParagraphStart.isOrphan())
282 nextParagraphStart = endOfParagraph(paragraphStart).next();
285 paragraphStart = nextParagraphStart;
286 nextParagraphStart = endOfParagraph(paragraphStart).next();
289 startRange = PlainTextRange(startIndex).createRangeForSelection(*toContainerNode(scope));
290 endRange = PlainTextRange(endIndex).createRangeForSelection(*toContainerNode(scope));
291 if (startRange && endRange)
292 updateStartEnd(startRange->startPosition(), endRange->startPosition());
295 static PassRefPtr<MutableStylePropertySet> copyStyleOrCreateEmpty(const StylePropertySet* style)
298 return MutableStylePropertySet::create();
299 return style->mutableCopy();
302 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style)
304 static const float MinimumFontSize = 0.1f;
306 if (!style || !style->hasFontSizeDelta())
309 Position start = startPosition();
310 Position end = endPosition();
311 if (comparePositions(end, start) < 0) {
312 Position swap = start;
317 // Join up any adjacent text nodes.
318 if (start.deprecatedNode()->isTextNode()) {
319 joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end);
320 start = startPosition();
324 if (start.isNull() || end.isNull())
327 if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) {
328 joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end);
329 start = startPosition();
333 if (start.isNull() || end.isNull())
336 // Split the start text nodes if needed to apply style.
337 if (isValidCaretPositionInTextNode(start)) {
338 splitTextAtStart(start, end);
339 start = startPosition();
343 if (isValidCaretPositionInTextNode(end)) {
344 splitTextAtEnd(start, end);
345 start = startPosition();
349 // Calculate loop end point.
350 // If the end node is before the start node (can only happen if the end node is
351 // an ancestor of the start node), we gather nodes up to the next sibling of the end node
353 if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode()))
354 beyondEnd = NodeTraversal::nextSkippingChildren(*end.deprecatedNode());
356 beyondEnd = NodeTraversal::next(*end.deprecatedNode());
358 start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
359 Node* startNode = start.deprecatedNode();
361 if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
362 startNode = NodeTraversal::next(*startNode);
364 // Store away font size before making any changes to the document.
365 // This ensures that changes to one node won't effect another.
366 HashMap<Node*, float> startingFontSizes;
367 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node))
368 startingFontSizes.set(node, computedFontSize(node));
370 // These spans were added by us. If empty after font size changes, they can be removed.
371 Vector<RefPtr<HTMLElement> > unstyledSpans;
373 Node* lastStyledNode = 0;
374 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(*node)) {
375 RefPtr<HTMLElement> element;
376 if (node->isHTMLElement()) {
377 // Only work on fully selected nodes.
378 if (!nodeFullySelected(node, start, end))
380 element = toHTMLElement(node);
381 } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
382 // Last styled node was not parent node of this text node, but we wish to style this
383 // text node. To make this possible, add a style span to surround this text node.
384 RefPtr<HTMLElement> span = createStyleSpanElement(document());
385 surroundNodeRangeWithElement(node, node, span.get());
386 element = span.release();
388 // Only handle HTML elements and text nodes.
391 lastStyledNode = node;
393 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
394 float currentFontSize = computedFontSize(node);
395 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta());
396 RefPtr<CSSValue> value = inlineStyle->getPropertyCSSValue(CSSPropertyFontSize);
398 element->removeInlineStyleProperty(CSSPropertyFontSize);
399 currentFontSize = computedFontSize(node);
401 if (currentFontSize != desiredFontSize) {
402 inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createValue(desiredFontSize, CSSPrimitiveValue::CSS_PX), false);
403 setNodeAttribute(element.get(), styleAttr, AtomicString(inlineStyle->asText()));
405 if (inlineStyle->isEmpty()) {
406 removeNodeAttribute(element.get(), styleAttr);
407 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get()))
408 unstyledSpans.append(element.release());
412 size_t size = unstyledSpans.size();
413 for (size_t i = 0; i < size; ++i)
414 removeNodePreservingChildren(unstyledSpans[i].get());
417 static Node* dummySpanAncestorForNode(const Node* node)
419 while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAttribute(toElement(node))))
420 node = node->parentNode();
422 return node ? node->parentNode() : 0;
425 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
427 if (!dummySpanAncestor)
430 // Dummy spans are created when text node is split, so that style information
431 // can be propagated, which can result in more splitting. If a dummy span gets
432 // cloned/split, the new node is always a sibling of it. Therefore, we scan
433 // all the children of the dummy's parent
435 for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
436 next = node->nextSibling();
437 if (isSpanWithoutAttributesOrUnstyledStyleSpan(node))
438 removeNodePreservingChildren(node);
442 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection)
444 // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
445 // In that case, we return the unsplit ancestor. Otherwise, we return 0.
446 Node* block = enclosingBlock(node);
450 Node* highestAncestorWithUnicodeBidi = 0;
451 Node* nextHighestAncestorWithUnicodeBidi = 0;
452 int highestAncestorUnicodeBidi = 0;
453 for (Node* n = node->parentNode(); n != block; n = n->parentNode()) {
454 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi);
455 if (unicodeBidi && unicodeBidi != CSSValueNormal) {
456 highestAncestorUnicodeBidi = unicodeBidi;
457 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
458 highestAncestorWithUnicodeBidi = n;
462 if (!highestAncestorWithUnicodeBidi)
465 HTMLElement* unsplitAncestor = 0;
467 WritingDirection highestAncestorDirection;
468 if (allowedDirection != NaturalWritingDirection
469 && highestAncestorUnicodeBidi != CSSValueBidiOverride
470 && highestAncestorWithUnicodeBidi->isHTMLElement()
471 && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection)
472 && highestAncestorDirection == allowedDirection) {
473 if (!nextHighestAncestorWithUnicodeBidi)
474 return toHTMLElement(highestAncestorWithUnicodeBidi);
476 unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi);
477 highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
480 // Split every ancestor through highest ancestor with embedding.
481 RefPtr<Node> currentNode = node;
482 while (currentNode) {
483 RefPtr<Element> parent = toElement(currentNode->parentNode());
484 if (before ? currentNode->previousSibling() : currentNode->nextSibling())
485 splitElement(parent, before ? currentNode : currentNode->nextSibling());
486 if (parent == highestAncestorWithUnicodeBidi)
488 currentNode = parent;
490 return unsplitAncestor;
493 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
495 Node* block = enclosingBlock(node);
499 for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = n->parentNode()) {
500 if (!n->isStyledElement())
503 Element* element = toElement(n);
504 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create(element).get(), CSSPropertyUnicodeBidi);
505 if (!unicodeBidi || unicodeBidi == CSSValueNormal)
508 // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
509 // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
510 // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
511 // otherwise it sets the property in the inline style declaration.
512 if (element->hasAttribute(dirAttr)) {
513 // FIXME: If this is a BDO element, we should probably just remove it if it has no
514 // other attributes, like we (should) do with B and I elements.
515 removeNodeAttribute(element, dirAttr);
517 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
518 inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
519 inlineStyle->removeProperty(CSSPropertyDirection);
520 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText()));
521 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
522 removeNodePreservingChildren(element);
527 static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode)
529 for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) {
530 if (n->isHTMLElement() && getIdentifierValue(CSSComputedStyleDeclaration::create(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed)
537 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style)
539 RefPtr<Node> startDummySpanAncestor = 0;
540 RefPtr<Node> endDummySpanAncestor = 0;
542 // update document layout once before removing styles
543 // so that we avoid the expense of updating before each and every call
544 // to check a computed style
545 document().updateLayoutIgnorePendingStylesheets();
547 // adjust to the positions we want to use for applying style
548 Position start = startPosition();
549 Position end = endPosition();
551 if (start.isNull() || end.isNull())
554 if (comparePositions(end, start) < 0) {
555 Position swap = start;
560 // split the start node and containing element if the selection starts inside of it
561 bool splitStart = isValidCaretPositionInTextNode(start);
563 if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style))
564 splitTextElementAtStart(start, end);
566 splitTextAtStart(start, end);
567 start = startPosition();
569 startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode());
572 // split the end node and containing element if the selection ends inside of it
573 bool splitEnd = isValidCaretPositionInTextNode(end);
575 if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style))
576 splitTextElementAtEnd(start, end);
578 splitTextAtEnd(start, end);
579 start = startPosition();
581 endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode());
584 // Remove style from the selection.
585 // Use the upstream position of the start for removing style.
586 // This will ensure we remove all traces of the relevant styles from the selection
587 // and prevent us from adding redundant ones, as described in:
588 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
589 Position removeStart = start.upstream();
590 WritingDirection textDirection = NaturalWritingDirection;
591 bool hasTextDirection = style->textDirection(textDirection);
592 RefPtr<EditingStyle> styleWithoutEmbedding;
593 RefPtr<EditingStyle> embeddingStyle;
594 if (hasTextDirection) {
595 // Leave alone an ancestor that provides the desired single level embedding, if there is one.
596 HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection);
597 HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection);
598 removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor);
599 removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor);
601 // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
602 Position embeddingRemoveStart = removeStart;
603 if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
604 embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
606 Position embeddingRemoveEnd = end;
607 if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
608 embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
610 if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
611 styleWithoutEmbedding = style->copy();
612 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
614 if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
615 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd);
619 removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end);
620 start = startPosition();
622 if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan())
625 if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) {
626 start = startPosition();
631 mergeEndWithNextIfIdentical(start, end);
632 start = startPosition();
636 // update document layout once before running the rest of the function
637 // so that we avoid the expense of updating before each and every call
638 // to check a computed style
639 document().updateLayoutIgnorePendingStylesheets();
641 RefPtr<EditingStyle> styleToApply = style;
642 if (hasTextDirection) {
643 // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
644 Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode()));
645 Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode()));
647 if (embeddingStartNode || embeddingEndNode) {
648 Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(embeddingStartNode) : start;
649 Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end;
650 ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull());
652 if (!embeddingStyle) {
653 styleWithoutEmbedding = style->copy();
654 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
656 fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
658 styleToApply = styleWithoutEmbedding;
662 fixRangeAndApplyInlineStyle(styleToApply.get(), start, end);
664 // Remove dummy style spans created by splitting text elements.
665 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor.get());
666 if (endDummySpanAncestor != startDummySpanAncestor)
667 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor.get());
670 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end)
672 Node* startNode = start.deprecatedNode();
675 if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) {
676 startNode = NodeTraversal::next(*startNode);
677 if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0)
681 Node* pastEndNode = end.deprecatedNode();
682 if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode()))
683 pastEndNode = NodeTraversal::nextSkippingChildren(*end.deprecatedNode());
685 // FIXME: Callers should perform this operation on a Range that includes the br
686 // if they want style applied to the empty line.
687 if (start == end && start.deprecatedNode()->hasTagName(brTag))
688 pastEndNode = NodeTraversal::next(*start.deprecatedNode());
690 // Start from the highest fully selected ancestor so that we can modify the fully selected node.
691 // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run
692 // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font>
693 RefPtr<Range> range = Range::create(startNode->document(), start, end);
694 Element* editableRoot = startNode->rootEditableElement();
695 if (startNode != editableRoot) {
696 while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get()))
697 startNode = startNode->parentNode();
700 applyInlineStyleToNodeRange(style, startNode, pastEndNode);
703 static bool containsNonEditableRegion(Node& node)
705 if (!node.rendererIsEditable())
708 Node* sibling = NodeTraversal::nextSkippingChildren(node);
709 for (Node* descendent = node.firstChild(); descendent && descendent != sibling; descendent = NodeTraversal::next(*descendent)) {
710 if (!descendent->rendererIsEditable())
717 struct InlineRunToApplyStyle {
718 InlineRunToApplyStyle(Node* start, Node* end, Node* pastEndNode)
721 , pastEndNode(pastEndNode)
723 ASSERT(start->parentNode() == end->parentNode());
726 bool startAndEndAreStillInDocument()
728 return start && end && start->inDocument() && end->inDocument();
733 RefPtr<Node> pastEndNode;
734 Position positionForStyleComputation;
735 RefPtr<Node> dummyElement;
739 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRefPtr<Node> startNode, PassRefPtr<Node> pastEndNode)
744 document().updateLayoutIgnorePendingStylesheets();
746 Vector<InlineRunToApplyStyle> runs;
747 RefPtr<Node> node = startNode;
748 for (RefPtr<Node> next; node && node != pastEndNode; node = next) {
749 next = NodeTraversal::next(*node);
751 if (!node->renderer() || !node->rendererIsEditable())
754 if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) {
755 // This is a plaintext-only region. Only proceed if it's fully selected.
756 // pastEndNode is the node after the last fully selected node, so if it's inside node then
757 // node isn't fully selected.
758 if (pastEndNode && pastEndNode->isDescendantOf(node.get()))
760 // Add to this element's inline style and skip over its contents.
761 HTMLElement* element = toHTMLElement(node);
762 next = NodeTraversal::nextSkippingChildren(*node);
765 RefPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCreateEmpty(element->inlineStyle());
766 inlineStyle->mergeAndOverrideOnConflict(style->style());
767 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asText()));
771 if (isBlock(node.get()))
774 if (node->childNodeCount()) {
775 if (node->contains(pastEndNode.get()) || containsNonEditableRegion(*node) || !node->parentNode()->rendererIsEditable())
777 if (editingIgnoresContent(node.get())) {
778 next = NodeTraversal::nextSkippingChildren(*node);
783 Node* runStart = node.get();
784 Node* runEnd = node.get();
785 Node* sibling = node->nextSibling();
786 while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode.get())
787 && (!isBlock(sibling) || sibling->hasTagName(brTag))
788 && !containsNonEditableRegion(*sibling)) {
790 sibling = runEnd->nextSibling();
793 next = NodeTraversal::nextSkippingChildren(*runEnd);
795 Node* pastEndNode = NodeTraversal::nextSkippingChildren(*runEnd);
796 if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode))
799 runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode));
802 for (size_t i = 0; i < runs.size(); i++) {
803 removeConflictingInlineStyleFromRun(style, runs[i].start, runs[i].end, runs[i].pastEndNode);
804 runs[i].positionForStyleComputation = positionToComputeInlineStyleChange(runs[i].start, runs[i].dummyElement);
807 document().updateLayoutIgnorePendingStylesheets();
809 for (size_t i = 0; i < runs.size(); i++)
810 runs[i].change = StyleChange(style, runs[i].positionForStyleComputation);
812 for (size_t i = 0; i < runs.size(); i++) {
813 InlineRunToApplyStyle& run = runs[i];
814 if (run.dummyElement)
815 removeNode(run.dummyElement);
816 if (run.startAndEndAreStillInDocument())
817 applyInlineStyleChange(run.start.release(), run.end.release(), run.change, AddStyledElement);
821 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
823 return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName()))
824 || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element));
827 bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* runStart, Node* pastEndNode)
829 ASSERT(style && runStart);
831 for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversal::next(*node)) {
832 if (node->childNodeCount())
834 // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified
835 if (!style->styleIsPresentInComputedStyleOfNode(node))
837 if (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))
843 void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd, PassRefPtr<Node> pastEndNode)
845 ASSERT(runStart && runEnd);
846 RefPtr<Node> next = runStart;
847 for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) {
848 if (editingIgnoresContent(node.get())) {
849 ASSERT(!node->contains(pastEndNode.get()));
850 next = NodeTraversal::nextSkippingChildren(*node);
852 next = NodeTraversal::next(*node);
854 if (!node->isHTMLElement())
857 RefPtr<Node> previousSibling = node->previousSibling();
858 RefPtr<Node> nextSibling = node->nextSibling();
859 RefPtr<ContainerNode> parent = node->parentNode();
860 removeInlineStyleFromElement(style, toHTMLElement(node), RemoveAlways);
861 if (!node->inDocument()) {
862 // FIXME: We might need to update the start and the end of current selection here but need a test.
863 if (runStart == node)
864 runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild();
866 runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild();
871 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
875 if (!element->parentNode() || !element->parentNode()->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))
878 if (isStyledInlineElementToRemove(element.get())) {
879 if (mode == RemoveNone)
881 ASSERT(extractedStyle);
882 extractedStyle->mergeInlineStyleOfElement(element.get(), EditingStyle::OverrideValues);
883 removeNodePreservingChildren(element);
887 bool removed = false;
888 if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle))
891 if (!element->inDocument())
894 // If the node was converted to a span, the span may still contain relevant
895 // styles which must be removed (e.g. <b style='font-weight: bold'>)
896 if (removeCSSStyle(style, element.get(), mode, extractedStyle))
902 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement* elem)
904 if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty))
905 removeNodePreservingChildren(elem);
907 replaceElementWithSpanPreservingChildrenAndAttributes(elem);
910 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
913 if (mode == RemoveNone) {
914 ASSERT(!extractedStyle);
915 return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element);
918 ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways);
919 if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) {
920 replaceWithSpanOrRemoveIfWithoutAttributes(element);
924 // unicode-bidi and direction are pushed down separately so don't push down with other styles
925 Vector<QualifiedName> attributes;
926 if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection,
927 extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle))
930 for (size_t i = 0; i < attributes.size(); i++)
931 removeNodeAttribute(element, attributes[i]);
933 if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(element))
934 removeNodePreservingChildren(element);
939 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
944 if (mode == RemoveNone)
945 return style->conflictsWithInlineStyleOfElement(element);
947 Vector<CSSPropertyID> properties;
948 if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties))
951 // FIXME: We should use a mass-removal function here but we don't have an undoable one yet.
952 for (size_t i = 0; i < properties.size(); i++)
953 removeCSSProperty(element, properties[i]);
955 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
956 removeNodePreservingChildren(element);
961 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node)
966 HTMLElement* result = 0;
967 Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node));
969 for (Node *n = node; n; n = n->parentNode()) {
970 if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n)))
971 result = toHTMLElement(n);
972 // Should stop at the editable root (cannot cross editing boundary) and
973 // also stop at the unsplittable element to be consistent with other UAs
974 if (n == unsplittableElement)
981 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style)
985 node->document().updateStyleIfNeeded();
987 if (!style || style->isEmpty() || !node->renderer())
990 RefPtr<EditingStyle> newInlineStyle = style;
991 if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) {
992 newInlineStyle = style->copy();
993 newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingStyle::OverrideValues);
996 // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
997 // FIXME: applyInlineStyleToRange should be used here instead.
998 if ((node->renderer()->isRenderBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) {
999 setNodeAttribute(toHTMLElement(node), styleAttr, AtomicString(newInlineStyle->style()->asText()));
1003 if (node->renderer()->isText() && toRenderText(node->renderer())->isAllCollapsibleWhitespace())
1006 // We can't wrap node with the styled element here because new styled element will never be removed if we did.
1007 // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element
1008 // then we fall into an infinite loop where we keep removing and adding styled element wrapping node.
1009 addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement);
1012 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode)
1014 HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode);
1015 if (!highestAncestor)
1018 // The outer loop is traversing the tree vertically from highestAncestor to targetNode
1019 RefPtr<Node> current = highestAncestor;
1020 // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown.
1021 // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
1022 Vector<RefPtr<Element> > elementsToPushDown;
1023 while (current && current != targetNode && current->contains(targetNode)) {
1024 NodeVector currentChildren;
1025 getChildNodes(*current, currentChildren);
1026 RefPtr<Element> styledElement;
1027 if (current->isStyledElement() && isStyledInlineElementToRemove(toElement(current))) {
1028 styledElement = toElement(current);
1029 elementsToPushDown.append(styledElement);
1032 RefPtr<EditingStyle> styleToPushDown = EditingStyle::create();
1033 if (current->isHTMLElement())
1034 removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get());
1036 // The inner loop will go through children on each level
1037 // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
1038 for (size_t i = 0; i < currentChildren.size(); ++i) {
1039 Node* child = currentChildren[i].get();
1040 if (!child->parentNode())
1042 if (!child->contains(targetNode) && elementsToPushDown.size()) {
1043 for (size_t i = 0; i < elementsToPushDown.size(); i++) {
1044 RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren();
1045 wrapper->removeAttribute(styleAttr);
1046 surroundNodeRangeWithElement(child, child, wrapper);
1050 // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode
1051 // But if we've removed styledElement then go ahead and always apply the style.
1052 if (child != targetNode || styledElement)
1053 applyInlineStyleToPushDown(child, styleToPushDown.get());
1055 // We found the next node for the outer loop (contains targetNode)
1056 // When reached targetNode, stop the outer loop upon the completion of the current inner loop
1057 if (child == targetNode || child->contains(targetNode))
1063 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end)
1065 ASSERT(start.isNotNull());
1066 ASSERT(end.isNotNull());
1067 ASSERT(start.inDocument());
1068 ASSERT(end.inDocument());
1069 ASSERT(comparePositions(start, end) <= 0);
1070 // FIXME: We should assert that start/end are not in the middle of a text node.
1072 Position pushDownStart = start.downstream();
1073 // If the pushDownStart is at the end of a text node, then this node is not fully selected.
1074 // Move it to the next deep quivalent position to avoid removing the style from this node.
1075 // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1076 Node* pushDownStartContainer = pushDownStart.containerNode();
1077 if (pushDownStartContainer && pushDownStartContainer->isTextNode()
1078 && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset())
1079 pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
1080 Position pushDownEnd = end.upstream();
1081 // If pushDownEnd is at the start of a text node, then this node is not fully selected.
1082 // Move it to the previous deep equivalent position to avoid removing the style from this node.
1083 Node* pushDownEndContainer = pushDownEnd.containerNode();
1084 if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownEnd.computeOffsetInContainerNode())
1085 pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd);
1087 pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode());
1088 pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode());
1090 // The s and e variables store the positions used to set the ending selection after style removal
1091 // takes place. This will help callers to recognize when either the start node or the end node
1092 // are removed from the document during the work of this function.
1093 // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(),
1094 // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune.
1095 Position s = start.isNull() || start.isOrphan() ? pushDownStart : start;
1096 Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end;
1098 RefPtr<Node> node = start.deprecatedNode();
1101 if (editingIgnoresContent(node.get())) {
1102 ASSERT(node == end.deprecatedNode() || !node->contains(end.deprecatedNode()));
1103 next = NodeTraversal::nextSkippingChildren(*node);
1105 next = NodeTraversal::next(*node);
1107 if (node->isHTMLElement() && nodeFullySelected(node.get(), start, end)) {
1108 RefPtr<HTMLElement> elem = toHTMLElement(node);
1109 RefPtr<Node> prev = NodeTraversal::previousPostOrder(*elem);
1110 RefPtr<Node> next = NodeTraversal::next(*elem);
1111 RefPtr<EditingStyle> styleToPushDown;
1112 RefPtr<Node> childNode;
1113 if (isStyledInlineElementToRemove(elem.get())) {
1114 styleToPushDown = EditingStyle::create();
1115 childNode = elem->firstChild();
1118 removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get());
1119 if (!elem->inDocument()) {
1120 if (s.deprecatedNode() == elem) {
1121 // Since elem must have been fully selected, and it is at the start
1122 // of the selection, it is clear we can set the new s offset to 0.
1123 ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0);
1124 s = firstPositionInOrBeforeNode(next.get());
1126 if (e.deprecatedNode() == elem) {
1127 // Since elem must have been fully selected, and it is at the end
1128 // of the selection, it is clear we can set the new e offset to
1129 // the max range offset of prev.
1130 ASSERT(s.anchorType() == Position::PositionIsAfterAnchor || !offsetIsBeforeLastNodeOffset(s.offsetInContainerNode(), s.containerNode()));
1131 e = lastPositionInOrAfterNode(prev.get());
1135 if (styleToPushDown) {
1136 for (; childNode; childNode = childNode->nextSibling())
1137 applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get());
1140 if (node == end.deprecatedNode())
1145 updateStartEnd(s, e);
1148 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
1151 ASSERT(node->isElementNode());
1153 // The tree may have changed and Position::upstream() relies on an up-to-date layout.
1154 node->document().updateLayoutIgnorePendingStylesheets();
1156 return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0
1157 && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0;
1160 void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end)
1162 ASSERT(start.containerNode()->isTextNode());
1165 if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode())
1166 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
1170 RefPtr<Text> text = start.containerText();
1171 splitTextNode(text, start.offsetInContainerNode());
1172 updateStartEnd(firstPositionInNode(text.get()), newEnd);
1175 void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end)
1177 ASSERT(end.containerNode()->isTextNode());
1179 bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode();
1180 Text* text = toText(end.deprecatedNode());
1181 splitTextNode(text, end.offsetInContainerNode());
1183 Node* prevNode = text->previousSibling();
1184 if (!prevNode || !prevNode->isTextNode())
1187 Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.offsetInContainerNode()) : start;
1188 updateStartEnd(newStart, lastPositionInNode(prevNode));
1191 void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end)
1193 ASSERT(start.containerNode()->isTextNode());
1196 if (start.containerNode() == end.containerNode())
1197 newEnd = Position(end.containerText(), end.offsetInContainerNode() - start.offsetInContainerNode());
1201 splitTextNodeContainingElement(start.containerText(), start.offsetInContainerNode());
1202 updateStartEnd(positionBeforeNode(start.containerNode()), newEnd);
1205 void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end)
1207 ASSERT(end.containerNode()->isTextNode());
1209 bool shouldUpdateStart = start.containerNode() == end.containerNode();
1210 splitTextNodeContainingElement(end.containerText(), end.offsetInContainerNode());
1212 Node* parentElement = end.containerNode()->parentNode();
1213 if (!parentElement || !parentElement->previousSibling())
1215 Node* firstTextNode = parentElement->previousSibling()->lastChild();
1216 if (!firstTextNode || !firstTextNode->isTextNode())
1219 Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), start.offsetInContainerNode()) : start;
1220 updateStartEnd(newStart, positionAfterNode(firstTextNode));
1223 bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style)
1225 if (!element || !element->isHTMLElement())
1228 return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element));
1231 bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position)
1233 Node* node = position.containerNode();
1234 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode())
1236 int offsetInText = position.offsetInContainerNode();
1237 return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node);
1240 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end)
1242 Node* startNode = start.containerNode();
1243 int startOffset = start.computeOffsetInContainerNode();
1247 if (isAtomicNode(startNode)) {
1248 // note: prior siblings could be unrendered elements. it's silly to miss the
1249 // merge opportunity just for that.
1250 if (startNode->previousSibling())
1253 startNode = startNode->parentNode();
1256 if (!startNode->isElementNode())
1259 Node* previousSibling = startNode->previousSibling();
1261 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1262 Element* previousElement = toElement(previousSibling);
1263 Element* element = toElement(startNode);
1264 Node* startChild = element->firstChild();
1266 mergeIdenticalElements(previousElement, element);
1268 int startOffsetAdjustment = startChild->nodeIndex();
1269 int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0;
1270 updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor),
1271 Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor));
1278 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end)
1280 Node* endNode = end.containerNode();
1282 if (isAtomicNode(endNode)) {
1283 int endOffset = end.computeOffsetInContainerNode();
1284 if (offsetIsBeforeLastNodeOffset(endOffset, endNode))
1287 if (end.deprecatedNode()->nextSibling())
1290 endNode = end.deprecatedNode()->parentNode();
1293 if (!endNode->isElementNode() || endNode->hasTagName(brTag))
1296 Node* nextSibling = endNode->nextSibling();
1297 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1298 Element* nextElement = toElement(nextSibling);
1299 Element* element = toElement(endNode);
1300 Node* nextChild = nextElement->firstChild();
1302 mergeIdenticalElements(element, nextElement);
1304 bool shouldUpdateStart = start.containerNode() == endNode;
1305 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1306 updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start,
1307 Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor));
1314 void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert)
1316 ASSERT(passedStartNode);
1318 ASSERT(elementToInsert);
1319 RefPtr<Node> node = passedStartNode;
1320 RefPtr<Element> element = elementToInsert;
1322 insertNodeBefore(element, node);
1325 RefPtr<Node> next = node->nextSibling();
1326 if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) {
1328 appendNode(node, element);
1330 if (node == endNode)
1335 RefPtr<Node> nextSibling = element->nextSibling();
1336 RefPtr<Node> previousSibling = element->previousSibling();
1337 if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable()
1338 && areIdenticalElements(element.get(), toElement(nextSibling)))
1339 mergeIdenticalElements(element.get(), toElement(nextSibling));
1341 if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) {
1342 Node* mergedElement = previousSibling->nextSibling();
1343 if (mergedElement->isElementNode() && mergedElement->rendererIsEditable()
1344 && areIdenticalElements(toElement(previousSibling), toElement(mergedElement)))
1345 mergeIdenticalElements(toElement(previousSibling), toElement(mergedElement));
1348 // FIXME: We should probably call updateStartEnd if the start or end was in the node
1349 // range so that the endingSelection() is canonicalized. See the comments at the end of
1350 // VisibleSelection::validate().
1353 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
1355 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1360 String cssStyle = styleChange.cssStyle();
1361 StringBuilder cssText;
1362 cssText.append(cssStyle);
1363 if (const StylePropertySet* decl = block->inlineStyle()) {
1364 if (!cssStyle.isEmpty())
1365 cssText.append(' ');
1366 cssText.append(decl->asText());
1368 setNodeAttribute(block, styleAttr, cssText.toAtomicString());
1371 void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement)
1373 if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument())
1376 RefPtr<Node> start = passedStart;
1377 RefPtr<Node> dummyElement;
1378 StyleChange styleChange(style, positionToComputeInlineStyleChange(start, dummyElement));
1381 removeNode(dummyElement);
1383 applyInlineStyleChange(start, passedEnd, styleChange, addStyledElement);
1386 Position ApplyStyleCommand::positionToComputeInlineStyleChange(PassRefPtr<Node> startNode, RefPtr<Node>& dummyElement)
1388 // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run.
1389 if (!startNode->isElementNode()) {
1390 dummyElement = createStyleSpanElement(document());
1391 insertNodeAt(dummyElement, positionBeforeNode(startNode.get()));
1392 return positionBeforeNode(dummyElement.get());
1395 return firstPositionInOrBeforeNode(startNode.get());
1398 void ApplyStyleCommand::applyInlineStyleChange(PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, StyleChange& styleChange, EAddStyledElement addStyledElement)
1400 RefPtr<Node> startNode = passedStart;
1401 RefPtr<Node> endNode = passedEnd;
1402 ASSERT(startNode->inDocument());
1403 ASSERT(endNode->inDocument());
1405 // Find appropriate font and span elements top-down.
1406 HTMLElement* fontContainer = 0;
1407 HTMLElement* styleContainer = 0;
1408 for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) {
1409 if (container->isHTMLElement() && container->hasTagName(fontTag))
1410 fontContainer = toHTMLElement(container);
1411 bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag);
1412 if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount())))
1413 styleContainer = toHTMLElement(container);
1414 if (!container->firstChild())
1416 startNode = container->firstChild();
1417 endNode = container->lastChild();
1420 // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1421 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1422 if (fontContainer) {
1423 if (styleChange.applyFontColor())
1424 setNodeAttribute(fontContainer, colorAttr, AtomicString(styleChange.fontColor()));
1425 if (styleChange.applyFontFace())
1426 setNodeAttribute(fontContainer, faceAttr, AtomicString(styleChange.fontFace()));
1427 if (styleChange.applyFontSize())
1428 setNodeAttribute(fontContainer, sizeAttr, AtomicString(styleChange.fontSize()));
1430 RefPtr<Element> fontElement = createFontElement(document());
1431 if (styleChange.applyFontColor())
1432 fontElement->setAttribute(colorAttr, AtomicString(styleChange.fontColor()));
1433 if (styleChange.applyFontFace())
1434 fontElement->setAttribute(faceAttr, AtomicString(styleChange.fontFace()));
1435 if (styleChange.applyFontSize())
1436 fontElement->setAttribute(sizeAttr, AtomicString(styleChange.fontSize()));
1437 surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
1441 if (styleChange.cssStyle().length()) {
1442 if (styleContainer) {
1443 if (const StylePropertySet* existingStyle = styleContainer->inlineStyle()) {
1444 String existingText = existingStyle->asText();
1445 StringBuilder cssText;
1446 cssText.append(existingText);
1447 if (!existingText.isEmpty())
1448 cssText.append(' ');
1449 cssText.append(styleChange.cssStyle());
1450 setNodeAttribute(styleContainer, styleAttr, cssText.toAtomicString());
1452 setNodeAttribute(styleContainer, styleAttr, AtomicString(styleChange.cssStyle()));
1455 RefPtr<Element> styleElement = createStyleSpanElement(document());
1456 styleElement->setAttribute(styleAttr, AtomicString(styleChange.cssStyle()));
1457 surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
1461 if (styleChange.applyBold())
1462 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));
1464 if (styleChange.applyItalic())
1465 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));
1467 if (styleChange.applyUnderline())
1468 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));
1470 if (styleChange.applyLineThrough())
1471 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), strikeTag));
1473 if (styleChange.applySubscript())
1474 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
1475 else if (styleChange.applySuperscript())
1476 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));
1478 if (m_styledInlineElement && addStyledElement == AddStyledElement)
1479 surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
1482 float ApplyStyleCommand::computedFontSize(Node* node)
1487 RefPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDeclaration::create(node);
1491 // FIXME: oilpan: Change to RefPtrWillBeRawPtr when changing CSSValue.
1492 RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(style->getPropertyCSSValue(CSSPropertyFontSize));
1496 return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1499 void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end)
1504 Position newStart = start;
1505 Position newEnd = end;
1507 Vector<RefPtr<Text> > textNodes;
1508 for (Node* curr = node->firstChild(); curr; curr = curr->nextSibling()) {
1509 if (!curr->isTextNode())
1512 textNodes.append(toText(curr));
1515 for (size_t i = 0; i < textNodes.size(); ++i) {
1516 Text* childText = textNodes[i].get();
1517 Node* next = childText->nextSibling();
1518 if (!next || !next->isTextNode())
1521 Text* nextText = toText(next);
1522 if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode())
1523 newStart = Position(childText, childText->length() + start.offsetInContainerNode());
1524 if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode())
1525 newEnd = Position(childText, childText->length() + end.offsetInContainerNode());
1526 String textToMove = nextText->data();
1527 insertTextIntoNode(childText, childText->length(), textToMove);
1529 // don't move child node pointer. it may want to merge with more text nodes.
1532 updateStartEnd(newStart, newEnd);