1 /*
2  * Copyright (C) 2005, 2006, 2008, 2009 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include "config.h"
27 #include "ApplyStyleCommand.h"
28 
29 #include "CSSComputedStyleDeclaration.h"
30 #include "CSSMutableStyleDeclaration.h"
31 #include "CSSParser.h"
32 #include "CSSProperty.h"
33 #include "CSSPropertyNames.h"
34 #include "CSSStyleSelector.h"
35 #include "CSSValueKeywords.h"
36 #include "CSSValueList.h"
37 #include "Document.h"
38 #include "EditingStyle.h"
39 #include "Editor.h"
40 #include "Frame.h"
41 #include "HTMLFontElement.h"
42 #include "HTMLInterchange.h"
43 #include "HTMLNames.h"
44 #include "NodeList.h"
45 #include "Range.h"
46 #include "RenderObject.h"
47 #include "Text.h"
48 #include "TextIterator.h"
49 #include "htmlediting.h"
50 #include "visible_units.h"
51 #include <wtf/StdLibExtras.h>
52 
53 namespace WebCore {
54 
55 using namespace HTMLNames;
56 
styleSpanClassString()57 static String& styleSpanClassString()
58 {
59     DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
60     return styleSpanClassString;
61 }
62 
isStyleSpan(const Node * node)63 bool isStyleSpan(const Node *node)
64 {
65     if (!node || !node->isHTMLElement())
66         return false;
67 
68     const HTMLElement* elem = static_cast<const HTMLElement*>(node);
69     return elem->hasLocalName(spanAttr) && elem->getAttribute(classAttr) == styleSpanClassString();
70 }
71 
isUnstyledStyleSpan(const Node * node)72 static bool isUnstyledStyleSpan(const Node* node)
73 {
74     if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
75         return false;
76 
77     const HTMLElement* elem = static_cast<const HTMLElement*>(node);
78     CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
79     return (!inlineStyleDecl || inlineStyleDecl->isEmpty()) && elem->getAttribute(classAttr) == styleSpanClassString();
80 }
81 
isSpanWithoutAttributesOrUnstyleStyleSpan(const Node * node)82 static bool isSpanWithoutAttributesOrUnstyleStyleSpan(const Node* node)
83 {
84     if (!node || !node->isHTMLElement() || !node->hasTagName(spanTag))
85         return false;
86 
87     const HTMLElement* elem = static_cast<const HTMLElement*>(node);
88     NamedNodeMap* attributes = elem->attributes(true); // readonly
89     if (attributes->isEmpty())
90         return true;
91 
92     return isUnstyledStyleSpan(node);
93 }
94 
isEmptyFontTag(const Node * node)95 static bool isEmptyFontTag(const Node *node)
96 {
97     if (!node || !node->hasTagName(fontTag))
98         return false;
99 
100     const Element *elem = static_cast<const Element *>(node);
101     NamedNodeMap *map = elem->attributes(true); // true for read-only
102     if (!map)
103         return true;
104     return map->isEmpty() || (map->length() == 1 && elem->getAttribute(classAttr) == styleSpanClassString());
105 }
106 
createFontElement(Document * document)107 static PassRefPtr<Element> createFontElement(Document* document)
108 {
109     RefPtr<Element> fontNode = createHTMLElement(document, fontTag);
110     fontNode->setAttribute(classAttr, styleSpanClassString());
111     return fontNode.release();
112 }
113 
createStyleSpanElement(Document * document)114 PassRefPtr<HTMLElement> createStyleSpanElement(Document* document)
115 {
116     RefPtr<HTMLElement> styleElement = createHTMLElement(document, spanTag);
117     styleElement->setAttribute(classAttr, styleSpanClassString());
118     return styleElement.release();
119 }
120 
ApplyStyleCommand(Document * document,const EditingStyle * style,EditAction editingAction,EPropertyLevel propertyLevel)121 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, EditAction editingAction, EPropertyLevel propertyLevel)
122     : CompositeEditCommand(document)
123     , m_style(style->copy())
124     , m_editingAction(editingAction)
125     , m_propertyLevel(propertyLevel)
126     , m_start(endingSelection().start().downstream())
127     , m_end(endingSelection().end().upstream())
128     , m_useEndingSelection(true)
129     , m_styledInlineElement(0)
130     , m_removeOnly(false)
131     , m_isInlineElementToRemoveFunction(0)
132 {
133 }
134 
ApplyStyleCommand(Document * document,const EditingStyle * style,const Position & start,const Position & end,EditAction editingAction,EPropertyLevel propertyLevel)135 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction, EPropertyLevel propertyLevel)
136     : CompositeEditCommand(document)
137     , m_style(style->copy())
138     , m_editingAction(editingAction)
139     , m_propertyLevel(propertyLevel)
140     , m_start(start)
141     , m_end(end)
142     , m_useEndingSelection(false)
143     , m_styledInlineElement(0)
144     , m_removeOnly(false)
145     , m_isInlineElementToRemoveFunction(0)
146 {
147 }
148 
ApplyStyleCommand(PassRefPtr<Element> element,bool removeOnly,EditAction editingAction)149 ApplyStyleCommand::ApplyStyleCommand(PassRefPtr<Element> element, bool removeOnly, EditAction editingAction)
150     : CompositeEditCommand(element->document())
151     , m_style(EditingStyle::create())
152     , m_editingAction(editingAction)
153     , m_propertyLevel(PropertyDefault)
154     , m_start(endingSelection().start().downstream())
155     , m_end(endingSelection().end().upstream())
156     , m_useEndingSelection(true)
157     , m_styledInlineElement(element)
158     , m_removeOnly(removeOnly)
159     , m_isInlineElementToRemoveFunction(0)
160 {
161 }
162 
ApplyStyleCommand(Document * document,const EditingStyle * style,IsInlineElementToRemoveFunction isInlineElementToRemoveFunction,EditAction editingAction)163 ApplyStyleCommand::ApplyStyleCommand(Document* document, const EditingStyle* style, IsInlineElementToRemoveFunction isInlineElementToRemoveFunction, EditAction editingAction)
164     : CompositeEditCommand(document)
165     , m_style(style->copy())
166     , m_editingAction(editingAction)
167     , m_propertyLevel(PropertyDefault)
168     , m_start(endingSelection().start().downstream())
169     , m_end(endingSelection().end().upstream())
170     , m_useEndingSelection(true)
171     , m_styledInlineElement(0)
172     , m_removeOnly(true)
173     , m_isInlineElementToRemoveFunction(isInlineElementToRemoveFunction)
174 {
175 }
176 
updateStartEnd(const Position & newStart,const Position & newEnd)177 void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& newEnd)
178 {
179     ASSERT(comparePositions(newEnd, newStart) >= 0);
180 
181     if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end))
182         m_useEndingSelection = true;
183 
184     setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY));
185     m_start = newStart;
186     m_end = newEnd;
187 }
188 
startPosition()189 Position ApplyStyleCommand::startPosition()
190 {
191     if (m_useEndingSelection)
192         return endingSelection().start();
193 
194     return m_start;
195 }
196 
endPosition()197 Position ApplyStyleCommand::endPosition()
198 {
199     if (m_useEndingSelection)
200         return endingSelection().end();
201 
202     return m_end;
203 }
204 
doApply()205 void ApplyStyleCommand::doApply()
206 {
207     switch (m_propertyLevel) {
208     case PropertyDefault: {
209         // Apply the block-centric properties of the style.
210         RefPtr<EditingStyle> blockStyle = m_style->extractAndRemoveBlockProperties();
211         if (!blockStyle->isEmpty())
212             applyBlockStyle(blockStyle.get());
213         // Apply any remaining styles to the inline elements.
214         if (!m_style->isEmpty() || m_styledInlineElement || m_isInlineElementToRemoveFunction) {
215             applyRelativeFontStyleChange(m_style.get());
216             applyInlineStyle(m_style.get());
217         }
218         break;
219     }
220     case ForceBlockProperties:
221         // Force all properties to be applied as block styles.
222         applyBlockStyle(m_style.get());
223         break;
224     }
225 }
226 
editingAction() const227 EditAction ApplyStyleCommand::editingAction() const
228 {
229     return m_editingAction;
230 }
231 
applyBlockStyle(EditingStyle * style)232 void ApplyStyleCommand::applyBlockStyle(EditingStyle *style)
233 {
234     // update document layout once before removing styles
235     // so that we avoid the expense of updating before each and every call
236     // to check a computed style
237     updateLayout();
238 
239     // get positions we want to use for applying style
240     Position start = startPosition();
241     Position end = endPosition();
242     if (comparePositions(end, start) < 0) {
243         Position swap = start;
244         start = end;
245         end = swap;
246     }
247 
248     VisiblePosition visibleStart(start);
249     VisiblePosition visibleEnd(end);
250 
251     if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
252         return;
253 
254     // Save and restore the selection endpoints using their indices in the document, since
255     // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoints.
256     // Calculate start and end indices from the start of the tree that they're in.
257     Node* scope = highestAncestor(visibleStart.deepEquivalent().deprecatedNode());
258     RefPtr<Range> startRange = Range::create(document(), firstPositionInNode(scope), visibleStart.deepEquivalent().parentAnchoredEquivalent());
259     RefPtr<Range> endRange = Range::create(document(), firstPositionInNode(scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
260     int startIndex = TextIterator::rangeLength(startRange.get(), true);
261     int endIndex = TextIterator::rangeLength(endRange.get(), true);
262 
263     VisiblePosition paragraphStart(startOfParagraph(visibleStart));
264     VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
265     VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
266     while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) {
267         StyleChange styleChange(style, paragraphStart.deepEquivalent());
268         if (styleChange.cssStyle().length() || m_removeOnly) {
269             RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode());
270             if (!m_removeOnly) {
271                 RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent());
272                 if (newBlock)
273                     block = newBlock;
274             }
275             ASSERT(block->isHTMLElement());
276             if (block->isHTMLElement()) {
277                 removeCSSStyle(style, toHTMLElement(block.get()));
278                 if (!m_removeOnly)
279                     addBlockStyle(styleChange, toHTMLElement(block.get()));
280             }
281 
282             if (nextParagraphStart.isOrphan())
283                 nextParagraphStart = endOfParagraph(paragraphStart).next();
284         }
285 
286         paragraphStart = nextParagraphStart;
287         nextParagraphStart = endOfParagraph(paragraphStart).next();
288     }
289 
290     startRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), startIndex, 0, true);
291     endRange = TextIterator::rangeFromLocationAndLength(static_cast<Element*>(scope), endIndex, 0, true);
292     if (startRange && endRange)
293         updateStartEnd(startRange->startPosition(), endRange->startPosition());
294 }
295 
applyRelativeFontStyleChange(EditingStyle * style)296 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style)
297 {
298     static const float MinimumFontSize = 0.1f;
299 
300     if (!style || !style->hasFontSizeDelta())
301         return;
302 
303     Position start = startPosition();
304     Position end = endPosition();
305     if (comparePositions(end, start) < 0) {
306         Position swap = start;
307         start = end;
308         end = swap;
309     }
310 
311     // Join up any adjacent text nodes.
312     if (start.deprecatedNode()->isTextNode()) {
313         joinChildTextNodes(start.deprecatedNode()->parentNode(), start, end);
314         start = startPosition();
315         end = endPosition();
316     }
317     if (end.deprecatedNode()->isTextNode() && start.deprecatedNode()->parentNode() != end.deprecatedNode()->parentNode()) {
318         joinChildTextNodes(end.deprecatedNode()->parentNode(), start, end);
319         start = startPosition();
320         end = endPosition();
321     }
322 
323     // Split the start text nodes if needed to apply style.
324     if (isValidCaretPositionInTextNode(start)) {
325         splitTextAtStart(start, end);
326         start = startPosition();
327         end = endPosition();
328     }
329 
330     if (isValidCaretPositionInTextNode(end)) {
331         splitTextAtEnd(start, end);
332         start = startPosition();
333         end = endPosition();
334     }
335 
336     // Calculate loop end point.
337     // If the end node is before the start node (can only happen if the end node is
338     // an ancestor of the start node), we gather nodes up to the next sibling of the end node
339     Node *beyondEnd;
340     if (start.deprecatedNode()->isDescendantOf(end.deprecatedNode()))
341         beyondEnd = end.deprecatedNode()->traverseNextSibling();
342     else
343         beyondEnd = end.deprecatedNode()->traverseNextNode();
344 
345     start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
346     Node* startNode = start.deprecatedNode();
347     if (startNode->isTextNode() && start.deprecatedEditingOffset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters.
348         startNode = startNode->traverseNextNode();
349 
350     // Store away font size before making any changes to the document.
351     // This ensures that changes to one node won't effect another.
352     HashMap<Node*, float> startingFontSizes;
353     for (Node *node = startNode; node != beyondEnd; node = node->traverseNextNode())
354         startingFontSizes.set(node, computedFontSize(node));
355 
356     // These spans were added by us. If empty after font size changes, they can be removed.
357     Vector<RefPtr<HTMLElement> > unstyledSpans;
358 
359     Node* lastStyledNode = 0;
360     for (Node* node = startNode; node != beyondEnd; node = node->traverseNextNode()) {
361         RefPtr<HTMLElement> element;
362         if (node->isHTMLElement()) {
363             // Only work on fully selected nodes.
364             if (!nodeFullySelected(node, start, end))
365                 continue;
366             element = toHTMLElement(node);
367         } else if (node->isTextNode() && node->renderer() && node->parentNode() != lastStyledNode) {
368             // Last styled node was not parent node of this text node, but we wish to style this
369             // text node. To make this possible, add a style span to surround this text node.
370             RefPtr<HTMLElement> span = createStyleSpanElement(document());
371             surroundNodeRangeWithElement(node, node, span.get());
372             element = span.release();
373         }  else {
374             // Only handle HTML elements and text nodes.
375             continue;
376         }
377         lastStyledNode = node;
378 
379         CSSMutableStyleDeclaration* inlineStyleDecl = element->getInlineStyleDecl();
380         float currentFontSize = computedFontSize(node);
381         float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta());
382         RefPtr<CSSValue> value = inlineStyleDecl->getPropertyCSSValue(CSSPropertyFontSize);
383         if (value) {
384             inlineStyleDecl->removeProperty(CSSPropertyFontSize, true);
385             currentFontSize = computedFontSize(node);
386         }
387         if (currentFontSize != desiredFontSize) {
388             inlineStyleDecl->setProperty(CSSPropertyFontSize, String::number(desiredFontSize) + "px", false, false);
389             setNodeAttribute(element.get(), styleAttr, inlineStyleDecl->cssText());
390         }
391         if (inlineStyleDecl->isEmpty()) {
392             removeNodeAttribute(element.get(), styleAttr);
393             // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
394             if (isUnstyledStyleSpan(element.get()))
395                 unstyledSpans.append(element.release());
396         }
397     }
398 
399     size_t size = unstyledSpans.size();
400     for (size_t i = 0; i < size; ++i)
401         removeNodePreservingChildren(unstyledSpans[i].get());
402 }
403 
dummySpanAncestorForNode(const Node * node)404 static Node* dummySpanAncestorForNode(const Node* node)
405 {
406     while (node && !isStyleSpan(node))
407         node = node->parentNode();
408 
409     return node ? node->parentNode() : 0;
410 }
411 
cleanupUnstyledAppleStyleSpans(Node * dummySpanAncestor)412 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(Node* dummySpanAncestor)
413 {
414     if (!dummySpanAncestor)
415         return;
416 
417     // Dummy spans are created when text node is split, so that style information
418     // can be propagated, which can result in more splitting. If a dummy span gets
419     // cloned/split, the new node is always a sibling of it. Therefore, we scan
420     // all the children of the dummy's parent
421     Node* next;
422     for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
423         next = node->nextSibling();
424         if (isUnstyledStyleSpan(node))
425             removeNodePreservingChildren(node);
426         node = next;
427     }
428 }
429 
splitAncestorsWithUnicodeBidi(Node * node,bool before,WritingDirection allowedDirection)430 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool before, WritingDirection allowedDirection)
431 {
432     // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
433     // In that case, we return the unsplit ancestor. Otherwise, we return 0.
434     Node* block = enclosingBlock(node);
435     if (!block)
436         return 0;
437 
438     Node* highestAncestorWithUnicodeBidi = 0;
439     Node* nextHighestAncestorWithUnicodeBidi = 0;
440     int highestAncestorUnicodeBidi = 0;
441     for (Node* n = node->parentNode(); n != block; n = n->parentNode()) {
442         int unicodeBidi = getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi);
443         if (unicodeBidi && unicodeBidi != CSSValueNormal) {
444             highestAncestorUnicodeBidi = unicodeBidi;
445             nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
446             highestAncestorWithUnicodeBidi = n;
447         }
448     }
449 
450     if (!highestAncestorWithUnicodeBidi)
451         return 0;
452 
453     HTMLElement* unsplitAncestor = 0;
454 
455     WritingDirection highestAncestorDirection;
456     if (allowedDirection != NaturalWritingDirection
457         && highestAncestorUnicodeBidi != CSSValueBidiOverride
458         && highestAncestorWithUnicodeBidi->isHTMLElement()
459         && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::AllProperties)->textDirection(highestAncestorDirection)
460         && highestAncestorDirection == allowedDirection) {
461         if (!nextHighestAncestorWithUnicodeBidi)
462             return toHTMLElement(highestAncestorWithUnicodeBidi);
463 
464         unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi);
465         highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
466     }
467 
468     // Split every ancestor through highest ancestor with embedding.
469     Node* n = node;
470     while (true) {
471         Element* parent = static_cast<Element*>(n->parentNode());
472         if (before ? n->previousSibling() : n->nextSibling())
473             splitElement(parent, before ? n : n->nextSibling());
474         if (parent == highestAncestorWithUnicodeBidi)
475             break;
476         n = n->parentNode();
477     }
478     return unsplitAncestor;
479 }
480 
removeEmbeddingUpToEnclosingBlock(Node * node,Node * unsplitAncestor)481 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, Node* unsplitAncestor)
482 {
483     Node* block = enclosingBlock(node);
484     if (!block)
485         return;
486 
487     Node* parent = 0;
488     for (Node* n = node->parentNode(); n != block && n != unsplitAncestor; n = parent) {
489         parent = n->parentNode();
490         if (!n->isStyledElement())
491             continue;
492 
493         StyledElement* element = static_cast<StyledElement*>(n);
494         int unicodeBidi = getIdentifierValue(computedStyle(element).get(), CSSPropertyUnicodeBidi);
495         if (!unicodeBidi || unicodeBidi == CSSValueNormal)
496             continue;
497 
498         // FIXME: This code should really consider the mapped attribute 'dir', the inline style declaration,
499         // and all matching style rules in order to determine how to best set the unicode-bidi property to 'normal'.
500         // For now, it assumes that if the 'dir' attribute is present, then removing it will suffice, and
501         // otherwise it sets the property in the inline style declaration.
502         if (element->hasAttribute(dirAttr)) {
503             // FIXME: If this is a BDO element, we should probably just remove it if it has no
504             // other attributes, like we (should) do with B and I elements.
505             removeNodeAttribute(element, dirAttr);
506         } else {
507             RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
508             inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
509             inlineStyle->removeProperty(CSSPropertyDirection);
510             setNodeAttribute(element, styleAttr, inlineStyle->cssText());
511             // FIXME: should this be isSpanWithoutAttributesOrUnstyleStyleSpan?  Need a test.
512             if (isUnstyledStyleSpan(element))
513                 removeNodePreservingChildren(element);
514         }
515     }
516 }
517 
highestEmbeddingAncestor(Node * startNode,Node * enclosingNode)518 static Node* highestEmbeddingAncestor(Node* startNode, Node* enclosingNode)
519 {
520     for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) {
521         if (n->isHTMLElement() && getIdentifierValue(computedStyle(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed)
522             return n;
523     }
524 
525     return 0;
526 }
527 
applyInlineStyle(EditingStyle * style)528 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style)
529 {
530     Node* startDummySpanAncestor = 0;
531     Node* endDummySpanAncestor = 0;
532 
533     style->collapseTextDecorationProperties();
534 
535     // update document layout once before removing styles
536     // so that we avoid the expense of updating before each and every call
537     // to check a computed style
538     updateLayout();
539 
540     // adjust to the positions we want to use for applying style
541     Position start = startPosition();
542     Position end = endPosition();
543     if (comparePositions(end, start) < 0) {
544         Position swap = start;
545         start = end;
546         end = swap;
547     }
548 
549     // split the start node and containing element if the selection starts inside of it
550     bool splitStart = isValidCaretPositionInTextNode(start);
551     if (splitStart) {
552         if (shouldSplitTextElement(start.deprecatedNode()->parentElement(), style))
553             splitTextElementAtStart(start, end);
554         else
555             splitTextAtStart(start, end);
556         start = startPosition();
557         end = endPosition();
558         startDummySpanAncestor = dummySpanAncestorForNode(start.deprecatedNode());
559     }
560 
561     // split the end node and containing element if the selection ends inside of it
562     bool splitEnd = isValidCaretPositionInTextNode(end);
563     if (splitEnd) {
564         if (shouldSplitTextElement(end.deprecatedNode()->parentElement(), style))
565             splitTextElementAtEnd(start, end);
566         else
567             splitTextAtEnd(start, end);
568         start = startPosition();
569         end = endPosition();
570         endDummySpanAncestor = dummySpanAncestorForNode(end.deprecatedNode());
571     }
572 
573     // Remove style from the selection.
574     // Use the upstream position of the start for removing style.
575     // This will ensure we remove all traces of the relevant styles from the selection
576     // and prevent us from adding redundant ones, as described in:
577     // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
578     Position removeStart = start.upstream();
579     WritingDirection textDirection = NaturalWritingDirection;
580     bool hasTextDirection = style->textDirection(textDirection);
581     RefPtr<EditingStyle> styleWithoutEmbedding;
582     RefPtr<EditingStyle> embeddingStyle;
583     if (hasTextDirection) {
584         // Leave alone an ancestor that provides the desired single level embedding, if there is one.
585         HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start.deprecatedNode(), true, textDirection);
586         HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.deprecatedNode(), false, textDirection);
587         removeEmbeddingUpToEnclosingBlock(start.deprecatedNode(), startUnsplitAncestor);
588         removeEmbeddingUpToEnclosingBlock(end.deprecatedNode(), endUnsplitAncestor);
589 
590         // Avoid removing the dir attribute and the unicode-bidi and direction properties from the unsplit ancestors.
591         Position embeddingRemoveStart = removeStart;
592         if (startUnsplitAncestor && nodeFullySelected(startUnsplitAncestor, removeStart, end))
593             embeddingRemoveStart = positionInParentAfterNode(startUnsplitAncestor);
594 
595         Position embeddingRemoveEnd = end;
596         if (endUnsplitAncestor && nodeFullySelected(endUnsplitAncestor, removeStart, end))
597             embeddingRemoveEnd = positionInParentBeforeNode(endUnsplitAncestor).downstream();
598 
599         if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
600             styleWithoutEmbedding = style->copy();
601             embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
602 
603             if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
604                 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, embeddingRemoveEnd);
605         }
606     }
607 
608     removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : style, removeStart, end);
609     start = startPosition();
610     end = endPosition();
611     if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan())
612         return;
613 
614     if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) {
615         start = startPosition();
616         end = endPosition();
617     }
618 
619     if (splitEnd) {
620         mergeEndWithNextIfIdentical(start, end);
621         start = startPosition();
622         end = endPosition();
623     }
624 
625     // update document layout once before running the rest of the function
626     // so that we avoid the expense of updating before each and every call
627     // to check a computed style
628     updateLayout();
629 
630     RefPtr<EditingStyle> styleToApply = style;
631     if (hasTextDirection) {
632         // Avoid applying the unicode-bidi and direction properties beneath ancestors that already have them.
633         Node* embeddingStartNode = highestEmbeddingAncestor(start.deprecatedNode(), enclosingBlock(start.deprecatedNode()));
634         Node* embeddingEndNode = highestEmbeddingAncestor(end.deprecatedNode(), enclosingBlock(end.deprecatedNode()));
635 
636         if (embeddingStartNode || embeddingEndNode) {
637             Position embeddingApplyStart = embeddingStartNode ? positionInParentAfterNode(embeddingStartNode) : start;
638             Position embeddingApplyEnd = embeddingEndNode ? positionInParentBeforeNode(embeddingEndNode) : end;
639             ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNull());
640 
641             if (!embeddingStyle) {
642                 styleWithoutEmbedding = style->copy();
643                 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirection();
644             }
645             fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStart, embeddingApplyEnd);
646 
647             styleToApply = styleWithoutEmbedding;
648         }
649     }
650 
651     fixRangeAndApplyInlineStyle(styleToApply.get(), start, end);
652 
653     // Remove dummy style spans created by splitting text elements.
654     cleanupUnstyledAppleStyleSpans(startDummySpanAncestor);
655     if (endDummySpanAncestor != startDummySpanAncestor)
656         cleanupUnstyledAppleStyleSpans(endDummySpanAncestor);
657 }
658 
fixRangeAndApplyInlineStyle(EditingStyle * style,const Position & start,const Position & end)659 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const Position& start, const Position& end)
660 {
661     Node* startNode = start.deprecatedNode();
662 
663     if (start.deprecatedEditingOffset() >= caretMaxOffset(start.deprecatedNode())) {
664         startNode = startNode->traverseNextNode();
665         if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(startNode)) < 0)
666             return;
667     }
668 
669     Node* pastEndNode = end.deprecatedNode();
670     if (end.deprecatedEditingOffset() >= caretMaxOffset(end.deprecatedNode()))
671         pastEndNode = end.deprecatedNode()->traverseNextSibling();
672 
673     // FIXME: Callers should perform this operation on a Range that includes the br
674     // if they want style applied to the empty line.
675     if (start == end && start.deprecatedNode()->hasTagName(brTag))
676         pastEndNode = start.deprecatedNode()->traverseNextNode();
677 
678     // Start from the highest fully selected ancestor so that we can modify the fully selected node.
679     // e.g. When applying font-size: large on <font color="blue">hello</font>, we need to include the font element in our run
680     // to generate <font color="blue" size="4">hello</font> instead of <font color="blue"><font size="4">hello</font></font>
681     RefPtr<Range> range = Range::create(startNode->document(), start, end);
682     Element* editableRoot = startNode->rootEditableElement();
683     if (startNode != editableRoot) {
684         while (editableRoot && startNode->parentNode() != editableRoot && isNodeVisiblyContainedWithin(startNode->parentNode(), range.get()))
685             startNode = startNode->parentNode();
686     }
687 
688     applyInlineStyleToNodeRange(style, startNode, pastEndNode);
689 }
690 
containsNonEditableRegion(Node * node)691 static bool containsNonEditableRegion(Node* node)
692 {
693     if (!node->rendererIsEditable())
694         return true;
695 
696     Node* sibling = node->traverseNextSibling();
697     for (Node* descendent = node->firstChild(); descendent && descendent != sibling; descendent = descendent->traverseNextNode()) {
698         if (!descendent->rendererIsEditable())
699             return true;
700     }
701 
702     return false;
703 }
704 
applyInlineStyleToNodeRange(EditingStyle * style,Node * node,Node * pastEndNode)705 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* node, Node* pastEndNode)
706 {
707     if (m_removeOnly)
708         return;
709 
710     for (RefPtr<Node> next; node && node != pastEndNode; node = next.get()) {
711         next = node->traverseNextNode();
712 
713         if (!node->renderer() || !node->rendererIsEditable())
714             continue;
715 
716         if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) {
717             // This is a plaintext-only region. Only proceed if it's fully selected.
718             // pastEndNode is the node after the last fully selected node, so if it's inside node then
719             // node isn't fully selected.
720             if (pastEndNode && pastEndNode->isDescendantOf(node))
721                 break;
722             // Add to this element's inline style and skip over its contents.
723             HTMLElement* element = toHTMLElement(node);
724             RefPtr<CSSMutableStyleDeclaration> inlineStyle = element->getInlineStyleDecl()->copy();
725             inlineStyle->merge(style->style());
726             setNodeAttribute(element, styleAttr, inlineStyle->cssText());
727             next = node->traverseNextSibling();
728             continue;
729         }
730 
731         if (isBlock(node))
732             continue;
733 
734         if (node->childNodeCount()) {
735             if (node->contains(pastEndNode) || containsNonEditableRegion(node) || !node->parentNode()->rendererIsEditable())
736                 continue;
737             if (editingIgnoresContent(node)) {
738                 next = node->traverseNextSibling();
739                 continue;
740             }
741         }
742 
743         RefPtr<Node> runStart = node;
744         RefPtr<Node> runEnd = node;
745         Node* sibling = node->nextSibling();
746         while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNode)
747                && (!isBlock(sibling) || sibling->hasTagName(brTag))
748                && !containsNonEditableRegion(sibling)) {
749             runEnd = sibling;
750             sibling = runEnd->nextSibling();
751         }
752         next = runEnd->traverseNextSibling();
753 
754         if (!removeStyleFromRunBeforeApplyingStyle(style, runStart, runEnd))
755             continue;
756         addInlineStyleIfNeeded(style, runStart.get(), runEnd.get(), AddStyledElement);
757     }
758 }
759 
isStyledInlineElementToRemove(Element * element) const760 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
761 {
762     return (m_styledInlineElement && element->hasTagName(m_styledInlineElement->tagQName()))
763         || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFunction(element));
764 }
765 
removeStyleFromRunBeforeApplyingStyle(EditingStyle * style,RefPtr<Node> & runStart,RefPtr<Node> & runEnd)766 bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(EditingStyle* style, RefPtr<Node>& runStart, RefPtr<Node>& runEnd)
767 {
768     ASSERT(runStart && runEnd && runStart->parentNode() == runEnd->parentNode());
769     RefPtr<Node> pastEndNode = runEnd->traverseNextSibling();
770     bool needToApplyStyle = false;
771     for (Node* node = runStart.get(); node && node != pastEndNode.get(); node = node->traverseNextNode()) {
772         if (node->childNodeCount())
773             continue;
774         // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified
775         if (!style->styleIsPresentInComputedStyleOfNode(node)
776             || (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))) {
777             needToApplyStyle = true;
778             break;
779         }
780     }
781     if (!needToApplyStyle)
782         return false;
783 
784     RefPtr<Node> next = runStart;
785     for (RefPtr<Node> node = next; node && node->inDocument() && node != pastEndNode; node = next) {
786         next = node->traverseNextNode();
787         if (!node->isHTMLElement())
788             continue;
789 
790         RefPtr<Node> previousSibling = node->previousSibling();
791         RefPtr<Node> nextSibling = node->nextSibling();
792         RefPtr<ContainerNode> parent = node->parentNode();
793         removeInlineStyleFromElement(style, toHTMLElement(node.get()), RemoveAlways);
794         if (!node->inDocument()) {
795             // FIXME: We might need to update the start and the end of current selection here but need a test.
796             if (runStart == node)
797                 runStart = previousSibling ? previousSibling->nextSibling() : parent->firstChild();
798             if (runEnd == node)
799                 runEnd = nextSibling ? nextSibling->previousSibling() : parent->lastChild();
800         }
801     }
802 
803     return true;
804 }
805 
removeInlineStyleFromElement(EditingStyle * style,PassRefPtr<HTMLElement> element,InlineStyleRemovalMode mode,EditingStyle * extractedStyle)806 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRefPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
807 {
808     ASSERT(element);
809 
810     if (!element->parentNode() || !element->parentNode()->rendererIsEditable())
811         return false;
812 
813     if (isStyledInlineElementToRemove(element.get())) {
814         if (mode == RemoveNone)
815             return true;
816         ASSERT(extractedStyle);
817         extractedStyle->mergeInlineStyleOfElement(element.get());
818         removeNodePreservingChildren(element);
819         return true;
820     }
821 
822     bool removed = false;
823     if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle))
824         removed = true;
825 
826     if (!element->inDocument())
827         return removed;
828 
829     // If the node was converted to a span, the span may still contain relevant
830     // styles which must be removed (e.g. <b style='font-weight: bold'>)
831     if (removeCSSStyle(style, element.get(), mode, extractedStyle))
832         removed = true;
833 
834     return removed;
835 }
836 
replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement * & elem)837 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement*& elem)
838 {
839     bool removeNode = false;
840 
841     // Similar to isSpanWithoutAttributesOrUnstyleStyleSpan, but does not look for Apple-style-span.
842     NamedNodeMap* attributes = elem->attributes(true); // readonly
843     if (!attributes || attributes->isEmpty())
844         removeNode = true;
845     else if (attributes->length() == 1 && elem->hasAttribute(styleAttr)) {
846         // Remove the element even if it has just style='' (this might be redundantly checked later too)
847         CSSMutableStyleDeclaration* inlineStyleDecl = elem->inlineStyleDecl();
848         if (!inlineStyleDecl || inlineStyleDecl->isEmpty())
849             removeNode = true;
850     }
851 
852     if (removeNode)
853         removeNodePreservingChildren(elem);
854     else {
855         HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(elem);
856         ASSERT(newSpanElement && newSpanElement->inDocument());
857         elem = newSpanElement;
858     }
859 }
860 
removeImplicitlyStyledElement(EditingStyle * style,HTMLElement * element,InlineStyleRemovalMode mode,EditingStyle * extractedStyle)861 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
862 {
863     ASSERT(style);
864     if (mode == RemoveNone) {
865         ASSERT(!extractedStyle);
866         return style->conflictsWithImplicitStyleOfElement(element) || style->conflictsWithImplicitStyleOfAttributes(element);
867     }
868 
869     ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways);
870     if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle)) {
871         replaceWithSpanOrRemoveIfWithoutAttributes(element);
872         return true;
873     }
874 
875     // unicode-bidi and direction are pushed down separately so don't push down with other styles
876     Vector<QualifiedName> attributes;
877     if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedStyle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritingDirection,
878         extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtractMatchingStyle))
879         return false;
880 
881     for (size_t i = 0; i < attributes.size(); i++)
882         removeNodeAttribute(element, attributes[i]);
883 
884     if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyleStyleSpan(element))
885         removeNodePreservingChildren(element);
886 
887     return true;
888 }
889 
removeCSSStyle(EditingStyle * style,HTMLElement * element,InlineStyleRemovalMode mode,EditingStyle * extractedStyle)890 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
891 {
892     ASSERT(style);
893     ASSERT(element);
894 
895     if (mode == RemoveNone)
896         return style->conflictsWithInlineStyleOfElement(element);
897 
898     Vector<CSSPropertyID> properties;
899     if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, properties))
900         return false;
901 
902     CSSMutableStyleDeclaration* inlineStyle = element->inlineStyleDecl();
903     ASSERT(inlineStyle);
904     // FIXME: We should use a mass-removal function here but we don't have an undoable one yet.
905     for (size_t i = 0; i < properties.size(); i++)
906         removeCSSProperty(element, properties[i]);
907 
908     // No need to serialize <foo style=""> if we just removed the last css property
909     if (inlineStyle->isEmpty())
910         removeNodeAttribute(element, styleAttr);
911 
912     if (isSpanWithoutAttributesOrUnstyleStyleSpan(element))
913         removeNodePreservingChildren(element);
914 
915     return true;
916 }
917 
highestAncestorWithConflictingInlineStyle(EditingStyle * style,Node * node)918 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(EditingStyle* style, Node* node)
919 {
920     if (!node)
921         return 0;
922 
923     HTMLElement* result = 0;
924     Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOrBeforeNode(node));
925 
926     for (Node *n = node; n; n = n->parentNode()) {
927         if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHTMLElement(n)))
928             result = toHTMLElement(n);
929         // Should stop at the editable root (cannot cross editing boundary) and
930         // also stop at the unsplittable element to be consistent with other UAs
931         if (n == unsplittableElement)
932             break;
933     }
934 
935     return result;
936 }
937 
applyInlineStyleToPushDown(Node * node,EditingStyle * style)938 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* style)
939 {
940     ASSERT(node);
941 
942     if (!style || style->isEmpty() || !node->renderer())
943         return;
944 
945     RefPtr<EditingStyle> newInlineStyle = style;
946     if (node->isHTMLElement() && static_cast<HTMLElement*>(node)->inlineStyleDecl()) {
947         newInlineStyle = style->copy();
948         newInlineStyle->mergeInlineStyleOfElement(static_cast<HTMLElement*>(node));
949     }
950 
951     // Since addInlineStyleIfNeeded can't add styles to block-flow render objects, add style attribute instead.
952     // FIXME: applyInlineStyleToRange should be used here instead.
953     if ((node->renderer()->isBlockFlow() || node->childNodeCount()) && node->isHTMLElement()) {
954         setNodeAttribute(toHTMLElement(node), styleAttr, newInlineStyle->style()->cssText());
955         return;
956     }
957 
958     if (node->renderer()->isText() && static_cast<RenderText*>(node->renderer())->isAllCollapsibleWhitespace())
959         return;
960 
961     // We can't wrap node with the styled element here because new styled element will never be removed if we did.
962     // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element
963     // then we fall into an infinite loop where we keep removing and adding styled element wrapping node.
964     addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement);
965 }
966 
pushDownInlineStyleAroundNode(EditingStyle * style,Node * targetNode)967 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode)
968 {
969     HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(style, targetNode);
970     if (!highestAncestor)
971         return;
972 
973     // The outer loop is traversing the tree vertically from highestAncestor to targetNode
974     Node* current = highestAncestor;
975     // Along the way, styled elements that contain targetNode are removed and accumulated into elementsToPushDown.
976     // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
977     Vector<RefPtr<Element> > elementsToPushDown;
978     while (current != targetNode) {
979         ASSERT(current);
980         ASSERT(current->contains(targetNode));
981         Node* child = current->firstChild();
982         Node* lastChild = current->lastChild();
983         RefPtr<StyledElement> styledElement;
984         if (current->isStyledElement() && isStyledInlineElementToRemove(static_cast<Element*>(current))) {
985             styledElement = static_cast<StyledElement*>(current);
986             elementsToPushDown.append(styledElement);
987         }
988 
989         RefPtr<EditingStyle> styleToPushDown = EditingStyle::create();
990         if (current->isHTMLElement())
991             removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get());
992 
993         // The inner loop will go through children on each level
994         // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
995         while (child) {
996             Node* nextChild = child->nextSibling();
997 
998             if (!child->contains(targetNode) && elementsToPushDown.size()) {
999                 for (size_t i = 0; i < elementsToPushDown.size(); i++) {
1000                     RefPtr<Element> wrapper = elementsToPushDown[i]->cloneElementWithoutChildren();
1001                     ExceptionCode ec = 0;
1002                     wrapper->removeAttribute(styleAttr, ec);
1003                     ASSERT(!ec);
1004                     surroundNodeRangeWithElement(child, child, wrapper);
1005                 }
1006             }
1007 
1008             // Apply text decoration to all nodes containing targetNode and their siblings but NOT to targetNode
1009             // But if we've removed styledElement then go ahead and always apply the style.
1010             if (child != targetNode || styledElement)
1011                 applyInlineStyleToPushDown(child, styleToPushDown.get());
1012 
1013             // We found the next node for the outer loop (contains targetNode)
1014             // When reached targetNode, stop the outer loop upon the completion of the current inner loop
1015             if (child == targetNode || child->contains(targetNode))
1016                 current = child;
1017 
1018             if (child == lastChild || child->contains(lastChild))
1019                 break;
1020             child = nextChild;
1021         }
1022     }
1023 }
1024 
removeInlineStyle(EditingStyle * style,const Position & start,const Position & end)1025 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &start, const Position &end)
1026 {
1027     ASSERT(start.isNotNull());
1028     ASSERT(end.isNotNull());
1029     ASSERT(start.anchorNode()->inDocument());
1030     ASSERT(end.anchorNode()->inDocument());
1031     ASSERT(comparePositions(start, end) <= 0);
1032 
1033     Position pushDownStart = start.downstream();
1034     // If the pushDownStart is at the end of a text node, then this node is not fully selected.
1035     // Move it to the next deep quivalent position to avoid removing the style from this node.
1036     // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1037     Node* pushDownStartContainer = pushDownStart.containerNode();
1038     if (pushDownStartContainer && pushDownStartContainer->isTextNode()
1039         && pushDownStart.computeOffsetInContainerNode() == pushDownStartContainer->maxCharacterOffset())
1040         pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
1041     Position pushDownEnd = end.upstream();
1042 
1043     pushDownInlineStyleAroundNode(style, pushDownStart.deprecatedNode());
1044     pushDownInlineStyleAroundNode(style, pushDownEnd.deprecatedNode());
1045 
1046     // The s and e variables store the positions used to set the ending selection after style removal
1047     // takes place. This will help callers to recognize when either the start node or the end node
1048     // are removed from the document during the work of this function.
1049     // If pushDownInlineStyleAroundNode has pruned start.deprecatedNode() or end.deprecatedNode(),
1050     // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAroundNode won't prune.
1051     Position s = start.isNull() || start.isOrphan() ? pushDownStart : start;
1052     Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end;
1053 
1054     Node* node = start.deprecatedNode();
1055     while (node) {
1056         RefPtr<Node> next = node->traverseNextNode();
1057         if (node->isHTMLElement() && nodeFullySelected(node, start, end)) {
1058             RefPtr<HTMLElement> elem = toHTMLElement(node);
1059             RefPtr<Node> prev = elem->traversePreviousNodePostOrder();
1060             RefPtr<Node> next = elem->traverseNextNode();
1061             RefPtr<EditingStyle> styleToPushDown;
1062             PassRefPtr<Node> childNode = 0;
1063             if (isStyledInlineElementToRemove(elem.get())) {
1064                 styleToPushDown = EditingStyle::create();
1065                 childNode = elem->firstChild();
1066             }
1067 
1068             removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styleToPushDown.get());
1069             if (!elem->inDocument()) {
1070                 if (s.deprecatedNode() == elem) {
1071                     // Since elem must have been fully selected, and it is at the start
1072                     // of the selection, it is clear we can set the new s offset to 0.
1073                     ASSERT(s.anchorType() == Position::PositionIsBeforeAnchor || s.offsetInContainerNode() <= 0);
1074                     s = firstPositionInOrBeforeNode(next.get());
1075                 }
1076                 if (e.deprecatedNode() == elem) {
1077                     // Since elem must have been fully selected, and it is at the end
1078                     // of the selection, it is clear we can set the new e offset to
1079                     // the max range offset of prev.
1080                     ASSERT(s.anchorType() == Position::PositionIsAfterAnchor
1081                            || s.offsetInContainerNode() >= lastOffsetInNode(s.containerNode()));
1082                     e = lastPositionInOrAfterNode(prev.get());
1083                 }
1084             }
1085 
1086             if (styleToPushDown) {
1087                 for (; childNode; childNode = childNode->nextSibling())
1088                     applyInlineStyleToPushDown(childNode.get(), styleToPushDown.get());
1089             }
1090         }
1091         if (node == end.deprecatedNode())
1092             break;
1093         node = next.get();
1094     }
1095 
1096     updateStartEnd(s, e);
1097 }
1098 
nodeFullySelected(Node * node,const Position & start,const Position & end) const1099 bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, const Position &end) const
1100 {
1101     ASSERT(node);
1102     ASSERT(node->isElementNode());
1103 
1104     return comparePositions(firstPositionInOrBeforeNode(node), start) >= 0
1105         && comparePositions(lastPositionInOrAfterNode(node).upstream(), end) <= 0;
1106 }
1107 
nodeFullyUnselected(Node * node,const Position & start,const Position & end) const1108 bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, const Position &end) const
1109 {
1110     ASSERT(node);
1111     ASSERT(node->isElementNode());
1112 
1113     bool isFullyBeforeStart = comparePositions(lastPositionInOrAfterNode(node).upstream(), start) < 0;
1114     bool isFullyAfterEnd = comparePositions(firstPositionInOrBeforeNode(node), end) > 0;
1115 
1116     return isFullyBeforeStart || isFullyAfterEnd;
1117 }
1118 
splitTextAtStart(const Position & start,const Position & end)1119 void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end)
1120 {
1121     ASSERT(start.anchorType() == Position::PositionIsOffsetInAnchor);
1122 
1123     Position newEnd;
1124     if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode())
1125         newEnd = Position(end.containerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor);
1126     else
1127         newEnd = end;
1128 
1129     Text* text = static_cast<Text*>(start.deprecatedNode());
1130     splitTextNode(text, start.offsetInContainerNode());
1131     updateStartEnd(firstPositionInNode(start.deprecatedNode()), newEnd);
1132 }
1133 
splitTextAtEnd(const Position & start,const Position & end)1134 void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& end)
1135 {
1136     ASSERT(end.anchorType() == Position::PositionIsOffsetInAnchor);
1137 
1138     bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode();
1139     Text* text = static_cast<Text *>(end.deprecatedNode());
1140     splitTextNode(text, end.offsetInContainerNode());
1141 
1142     Node* prevNode = text->previousSibling();
1143     ASSERT(prevNode);
1144     Position newStart = shouldUpdateStart ? Position(prevNode, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start;
1145     updateStartEnd(newStart, lastPositionInNode(prevNode));
1146 }
1147 
splitTextElementAtStart(const Position & start,const Position & end)1148 void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Position& end)
1149 {
1150     ASSERT(start.anchorType() == Position::PositionIsOffsetInAnchor);
1151 
1152     Position newEnd;
1153     if (end.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode())
1154         newEnd = Position(end.containerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor);
1155     else
1156         newEnd = end;
1157 
1158     Text* text = static_cast<Text*>(start.deprecatedNode());
1159     splitTextNodeContainingElement(text, start.deprecatedEditingOffset());
1160     updateStartEnd(Position(start.deprecatedNode()->parentNode(), start.deprecatedNode()->nodeIndex(), Position::PositionIsOffsetInAnchor), newEnd);
1161 }
1162 
splitTextElementAtEnd(const Position & start,const Position & end)1163 void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Position& end)
1164 {
1165     ASSERT(end.anchorType() == Position::PositionIsOffsetInAnchor);
1166 
1167     bool shouldUpdateStart = start.anchorType() == Position::PositionIsOffsetInAnchor && start.containerNode() == end.containerNode();
1168     Text* text = static_cast<Text*>(end.deprecatedNode());
1169     splitTextNodeContainingElement(text, end.deprecatedEditingOffset());
1170 
1171     Node* prevNode = text->parentNode()->previousSibling()->lastChild();
1172     ASSERT(prevNode);
1173     Position newStart = shouldUpdateStart ? Position(prevNode, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start;
1174     updateStartEnd(newStart, Position(prevNode->parentNode(), prevNode->nodeIndex() + 1, Position::PositionIsOffsetInAnchor));
1175 }
1176 
shouldSplitTextElement(Element * element,EditingStyle * style)1177 bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* style)
1178 {
1179     if (!element || !element->isHTMLElement())
1180         return false;
1181 
1182     return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element));
1183 }
1184 
isValidCaretPositionInTextNode(const Position & position)1185 bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position)
1186 {
1187     Node* node = position.containerNode();
1188     if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node->isTextNode())
1189         return false;
1190     int offsetInText = position.offsetInContainerNode();
1191     return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset(node);
1192 }
1193 
areIdenticalElements(Node * first,Node * second)1194 static bool areIdenticalElements(Node *first, Node *second)
1195 {
1196     // check that tag name and all attribute names and values are identical
1197 
1198     if (!first->isElementNode())
1199         return false;
1200 
1201     if (!second->isElementNode())
1202         return false;
1203 
1204     Element *firstElement = static_cast<Element *>(first);
1205     Element *secondElement = static_cast<Element *>(second);
1206 
1207     if (!firstElement->tagQName().matches(secondElement->tagQName()))
1208         return false;
1209 
1210     NamedNodeMap *firstMap = firstElement->attributes();
1211     NamedNodeMap *secondMap = secondElement->attributes();
1212 
1213     unsigned firstLength = firstMap->length();
1214 
1215     if (firstLength != secondMap->length())
1216         return false;
1217 
1218     for (unsigned i = 0; i < firstLength; i++) {
1219         Attribute *attribute = firstMap->attributeItem(i);
1220         Attribute *secondAttribute = secondMap->getAttributeItem(attribute->name());
1221 
1222         if (!secondAttribute || attribute->value() != secondAttribute->value())
1223             return false;
1224     }
1225 
1226     return true;
1227 }
1228 
mergeStartWithPreviousIfIdentical(const Position & start,const Position & end)1229 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end)
1230 {
1231     Node* startNode = start.containerNode();
1232     int startOffset = start.computeOffsetInContainerNode();
1233     if (startOffset)
1234         return false;
1235 
1236     if (isAtomicNode(startNode)) {
1237         // note: prior siblings could be unrendered elements. it's silly to miss the
1238         // merge opportunity just for that.
1239         if (startNode->previousSibling())
1240             return false;
1241 
1242         startNode = startNode->parentNode();
1243         startOffset = 0;
1244     }
1245 
1246     if (!startNode->isElementNode())
1247         return false;
1248 
1249     Node* previousSibling = startNode->previousSibling();
1250 
1251     if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1252         Element* previousElement = static_cast<Element*>(previousSibling);
1253         Element* element = static_cast<Element*>(startNode);
1254         Node* startChild = element->firstChild();
1255         ASSERT(startChild);
1256         mergeIdenticalElements(previousElement, element);
1257 
1258         int startOffsetAdjustment = startChild->nodeIndex();
1259         int endOffsetAdjustment = startNode == end.deprecatedNode() ? startOffsetAdjustment : 0;
1260         updateStartEnd(Position(startNode, startOffsetAdjustment, Position::PositionIsOffsetInAnchor),
1261                        Position(end.deprecatedNode(), end.deprecatedEditingOffset() + endOffsetAdjustment, Position::PositionIsOffsetInAnchor));
1262         return true;
1263     }
1264 
1265     return false;
1266 }
1267 
mergeEndWithNextIfIdentical(const Position & start,const Position & end)1268 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end)
1269 {
1270     Node* endNode = end.containerNode();
1271     int endOffset = end.computeOffsetInContainerNode();
1272 
1273     if (isAtomicNode(endNode)) {
1274         if (endOffset < lastOffsetInNode(endNode))
1275             return false;
1276 
1277         unsigned parentLastOffset = end.deprecatedNode()->parentNode()->childNodes()->length() - 1;
1278         if (end.deprecatedNode()->nextSibling())
1279             return false;
1280 
1281         endNode = end.deprecatedNode()->parentNode();
1282         endOffset = parentLastOffset;
1283     }
1284 
1285     if (!endNode->isElementNode() || endNode->hasTagName(brTag))
1286         return false;
1287 
1288     Node* nextSibling = endNode->nextSibling();
1289     if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1290         Element* nextElement = static_cast<Element *>(nextSibling);
1291         Element* element = static_cast<Element *>(endNode);
1292         Node* nextChild = nextElement->firstChild();
1293 
1294         mergeIdenticalElements(element, nextElement);
1295 
1296         bool shouldUpdateStart = start.containerNode() == endNode;
1297         int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length();
1298         updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor) : start,
1299                        Position(nextElement, endOffset, Position::PositionIsOffsetInAnchor));
1300         return true;
1301     }
1302 
1303     return false;
1304 }
1305 
surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode,PassRefPtr<Node> endNode,PassRefPtr<Element> elementToInsert)1306 void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStartNode, PassRefPtr<Node> endNode, PassRefPtr<Element> elementToInsert)
1307 {
1308     ASSERT(passedStartNode);
1309     ASSERT(endNode);
1310     ASSERT(elementToInsert);
1311     RefPtr<Node> startNode = passedStartNode;
1312     RefPtr<Element> element = elementToInsert;
1313 
1314     insertNodeBefore(element, startNode);
1315 
1316     RefPtr<Node> node = startNode;
1317     while (node) {
1318         RefPtr<Node> next = node->nextSibling();
1319         if (node->isContentEditable()) {
1320             removeNode(node);
1321             appendNode(node, element);
1322         }
1323         if (node == endNode)
1324             break;
1325         node = next;
1326     }
1327 
1328     RefPtr<Node> nextSibling = element->nextSibling();
1329     RefPtr<Node> previousSibling = element->previousSibling();
1330     if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable()
1331         && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get())))
1332         mergeIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get()));
1333 
1334     if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) {
1335         Node* mergedElement = previousSibling->nextSibling();
1336         if (mergedElement->isElementNode() && mergedElement->rendererIsEditable()
1337             && areIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement)))
1338             mergeIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement));
1339     }
1340 
1341     // FIXME: We should probably call updateStartEnd if the start or end was in the node
1342     // range so that the endingSelection() is canonicalized.  See the comments at the end of
1343     // VisibleSelection::validate().
1344 }
1345 
addBlockStyle(const StyleChange & styleChange,HTMLElement * block)1346 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block)
1347 {
1348     // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1349     // inline content.
1350     if (!block)
1351         return;
1352 
1353     String cssText = styleChange.cssStyle();
1354     CSSMutableStyleDeclaration* decl = block->inlineStyleDecl();
1355     if (decl)
1356         cssText += decl->cssText();
1357     setNodeAttribute(block, styleAttr, cssText);
1358 }
1359 
addInlineStyleIfNeeded(EditingStyle * style,PassRefPtr<Node> passedStart,PassRefPtr<Node> passedEnd,EAddStyledElement addStyledElement)1360 void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement)
1361 {
1362     if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument())
1363         return;
1364     RefPtr<Node> startNode = passedStart;
1365     RefPtr<Node> endNode = passedEnd;
1366 
1367     // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run.
1368     RefPtr<HTMLElement> dummyElement;
1369     Position positionForStyleComparison;
1370     if (!startNode->isElementNode()) {
1371         dummyElement = createStyleSpanElement(document());
1372         insertNodeAt(dummyElement, positionBeforeNode(startNode.get()));
1373         positionForStyleComparison = positionBeforeNode(dummyElement.get());
1374     } else
1375         positionForStyleComparison = firstPositionInOrBeforeNode(startNode.get());
1376 
1377     StyleChange styleChange(style, positionForStyleComparison);
1378 
1379     if (dummyElement)
1380         removeNode(dummyElement);
1381 
1382     // Find appropriate font and span elements top-down.
1383     HTMLElement* fontContainer = 0;
1384     HTMLElement* styleContainer = 0;
1385     for (Node* container = startNode.get(); container && startNode == endNode; container = container->firstChild()) {
1386         if (container->isHTMLElement() && container->hasTagName(fontTag))
1387             fontContainer = toHTMLElement(container);
1388         bool styleContainerIsNotSpan = !styleContainer || !styleContainer->hasTagName(spanTag);
1389         if (container->isHTMLElement() && (container->hasTagName(spanTag) || (styleContainerIsNotSpan && container->childNodeCount())))
1390             styleContainer = toHTMLElement(container);
1391         if (!container->firstChild())
1392             break;
1393         startNode = container->firstChild();
1394         endNode = container->lastChild();
1395     }
1396 
1397     // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes.
1398     if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChange.applyFontSize()) {
1399         if (fontContainer) {
1400             if (styleChange.applyFontColor())
1401                 setNodeAttribute(fontContainer, colorAttr, styleChange.fontColor());
1402             if (styleChange.applyFontFace())
1403                 setNodeAttribute(fontContainer, faceAttr, styleChange.fontFace());
1404             if (styleChange.applyFontSize())
1405                 setNodeAttribute(fontContainer, sizeAttr, styleChange.fontSize());
1406         } else {
1407             RefPtr<Element> fontElement = createFontElement(document());
1408             if (styleChange.applyFontColor())
1409                 fontElement->setAttribute(colorAttr, styleChange.fontColor());
1410             if (styleChange.applyFontFace())
1411                 fontElement->setAttribute(faceAttr, styleChange.fontFace());
1412             if (styleChange.applyFontSize())
1413                 fontElement->setAttribute(sizeAttr, styleChange.fontSize());
1414             surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
1415         }
1416     }
1417 
1418     if (styleChange.cssStyle().length()) {
1419         if (styleContainer) {
1420             CSSMutableStyleDeclaration* existingStyle = toHTMLElement(styleContainer)->inlineStyleDecl();
1421             if (existingStyle)
1422                 setNodeAttribute(styleContainer, styleAttr, existingStyle->cssText() + styleChange.cssStyle());
1423             else
1424                 setNodeAttribute(styleContainer, styleAttr, styleChange.cssStyle());
1425         } else {
1426             RefPtr<Element> styleElement = createStyleSpanElement(document());
1427             styleElement->setAttribute(styleAttr, styleChange.cssStyle());
1428             surroundNodeRangeWithElement(startNode, endNode, styleElement.release());
1429         }
1430     }
1431 
1432     if (styleChange.applyBold())
1433         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), bTag));
1434 
1435     if (styleChange.applyItalic())
1436         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), iTag));
1437 
1438     if (styleChange.applyUnderline())
1439         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), uTag));
1440 
1441     if (styleChange.applyLineThrough())
1442         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), strikeTag));
1443 
1444     if (styleChange.applySubscript())
1445         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), subTag));
1446     else if (styleChange.applySuperscript())
1447         surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag));
1448 
1449     if (m_styledInlineElement && addStyledElement == AddStyledElement)
1450         surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren());
1451 }
1452 
computedFontSize(Node * node)1453 float ApplyStyleCommand::computedFontSize(Node* node)
1454 {
1455     if (!node)
1456         return 0;
1457 
1458     RefPtr<CSSComputedStyleDeclaration> style = computedStyle(node);
1459     if (!style)
1460         return 0;
1461 
1462     RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(style->getPropertyCSSValue(CSSPropertyFontSize));
1463     if (!value)
1464         return 0;
1465 
1466     return value->getFloatValue(CSSPrimitiveValue::CSS_PX);
1467 }
1468 
joinChildTextNodes(Node * node,const Position & start,const Position & end)1469 void ApplyStyleCommand::joinChildTextNodes(Node* node, const Position& start, const Position& end)
1470 {
1471     if (!node)
1472         return;
1473 
1474     Position newStart = start;
1475     Position newEnd = end;
1476 
1477     Node* child = node->firstChild();
1478     while (child) {
1479         Node* next = child->nextSibling();
1480         if (child->isTextNode() && next && next->isTextNode()) {
1481             Text* childText = static_cast<Text *>(child);
1482             Text* nextText = static_cast<Text *>(next);
1483             if (start.anchorType() == Position::PositionIsOffsetInAnchor && next == start.containerNode())
1484                 newStart = Position(childText, childText->length() + start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor);
1485             if (end.anchorType() == Position::PositionIsOffsetInAnchor && next == end.containerNode())
1486                 newEnd = Position(childText, childText->length() + end.offsetInContainerNode(), Position::PositionIsOffsetInAnchor);
1487             String textToMove = nextText->data();
1488             insertTextIntoNode(childText, childText->length(), textToMove);
1489             removeNode(next);
1490             // don't move child node pointer. it may want to merge with more text nodes.
1491         }
1492         else {
1493             child = child->nextSibling();
1494         }
1495     }
1496 
1497     updateStartEnd(newStart, newEnd);
1498 }
1499 
1500 }
1501