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