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