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