Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(57)

Side by Side Diff: Source/core/editing/ApplyStyleCommand.cpp

Issue 1294543005: Move execCommand related files in core/editing/ related files into core/editing/commands/ (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: 2015-08-18T14:20:58 Rebase for merging code style fixes Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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/EditingUtilities.h"
42 #include "core/editing/PlainTextRange.h"
43 #include "core/editing/VisibleUnits.h"
44 #include "core/editing/iterators/TextIterator.h"
45 #include "core/editing/serializers/HTMLInterchange.h"
46 #include "core/frame/UseCounter.h"
47 #include "core/html/HTMLFontElement.h"
48 #include "core/html/HTMLSpanElement.h"
49 #include "core/layout/LayoutObject.h"
50 #include "core/layout/LayoutText.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
59 static String& styleSpanClassString()
60 {
61 DEFINE_STATIC_LOCAL(String, styleSpanClassString, ((AppleStyleSpanClass)));
62 return styleSpanClassString;
63 }
64
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
77 static bool hasNoAttributeOrOnlyStyleAttribute(const HTMLElement* element, Shoul dStyleAttributeBeEmpty 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 == Allo wNonEmptyStyleAttribute
87 || !element->inlineStyle() || element->inlineStyle()->isEmpty()))
88 matchedAttributes++;
89
90 ASSERT(matchedAttributes <= attributes.size());
91 return matchedAttributes == attributes.size();
92 }
93
94 bool isStyleSpanOrSpanWithOnlyStyleAttribute(const Element* element)
95 {
96 if (!isHTMLSpanElement(element))
97 return false;
98 return hasNoAttributeOrOnlyStyleAttribute(toHTMLSpanElement(element), AllowN onEmptyStyleAttribute);
99 }
100
101 static inline bool isSpanWithoutAttributesOrUnstyledStyleSpan(const Node* node)
102 {
103 if (!isHTMLSpanElement(node))
104 return false;
105 return hasNoAttributeOrOnlyStyleAttribute(toHTMLSpanElement(node), StyleAttr ibuteShouldBeEmpty);
106 }
107
108 bool isEmptyFontTag(const Element* element, ShouldStyleAttributeBeEmpty shouldSt yleAttributeBeEmpty)
109 {
110 if (!isHTMLFontElement(element))
111 return false;
112
113 return hasNoAttributeOrOnlyStyleAttribute(toHTMLFontElement(element), should StyleAttributeBeEmpty);
114 }
115
116 static PassRefPtrWillBeRawPtr<HTMLFontElement> createFontElement(Document& docum ent)
117 {
118 return toHTMLFontElement(createHTMLElement(document, fontTag).get());
119 }
120
121 PassRefPtrWillBeRawPtr<HTMLSpanElement> createStyleSpanElement(Document& documen t)
122 {
123 return toHTMLSpanElement(createHTMLElement(document, spanTag).get());
124 }
125
126 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* sty le, 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
140 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* sty le, const Position& start, const Position& end, EditAction editingAction, EPrope rtyLevel 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
154 ApplyStyleCommand::ApplyStyleCommand(PassRefPtrWillBeRawPtr<Element> element, bo ol 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
168 ApplyStyleCommand::ApplyStyleCommand(Document& document, const EditingStyle* sty le, 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
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, e ndingSelection().isDirectional()));
190 m_start = newStart;
191 m_end = newEnd;
192 }
193
194 Position ApplyStyleCommand::startPosition()
195 {
196 if (m_useEndingSelection)
197 return endingSelection().start();
198
199 return m_start;
200 }
201
202 Position ApplyStyleCommand::endPosition()
203 {
204 if (m_useEndingSelection)
205 return endingSelection().end();
206
207 return m_end;
208 }
209
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->extractAndRemoveB lockProperties();
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_isInlineElementToR emoveFunction) {
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
232 EditAction ApplyStyleCommand::editingAction() const
233 {
234 return m_editingAction;
235 }
236
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 docum ent, since
260 // addBlockStyleIfNeeded may moveParagraphs, which can remove these endpoint s.
261 // Calculate start and end indices from the start of the tree that they're i n.
262 Node& scope = NodeTraversal::highestAncestorOrSelf(*visibleStart.deepEquival ent().anchorNode());
263 RefPtrWillBeRawPtr<Range> startRange = Range::create(document(), firstPositi onInNode(&scope), visibleStart.deepEquivalent().parentAnchoredEquivalent());
264 RefPtrWillBeRawPtr<Range> endRange = Range::create(document(), firstPosition InNode(&scope), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
265 int startIndex = TextIterator::rangeLength(startRange->startPosition(), star tRange->endPosition(), true);
266 int endIndex = TextIterator::rangeLength(endRange->startPosition(), endRange ->endPosition(), true);
267
268 VisiblePosition paragraphStart(startOfParagraph(visibleStart));
269 VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next());
270 VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next());
271 while (paragraphStart.isNotNull() && paragraphStart.deepEquivalent() != beyo ndEnd.deepEquivalent()) {
272 StyleChange styleChange(style, paragraphStart.deepEquivalent());
273 if (styleChange.cssStyle().length() || m_removeOnly) {
274 RefPtrWillBeRawPtr<Element> block = enclosingBlock(paragraphStart.de epEquivalent().anchorNode());
275 const Position& paragraphStartToMove = paragraphStart.deepEquivalent ();
276 if (!m_removeOnly && isEditablePosition(paragraphStartToMove)) {
277 RefPtrWillBeRawPtr<HTMLElement> newBlock = moveParagraphContents ToNewBlockIfNecessary(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 EphemeralRange startEphemeralRange = PlainTextRange(startIndex).createRangeF orSelection(toContainerNode(scope));
296 if (startEphemeralRange.isNull())
297 return;
298 EphemeralRange endEphemeralRange = PlainTextRange(endIndex).createRangeForSe lection(toContainerNode(scope));
299 if (endEphemeralRange.isNull())
300 return;
301 updateStartEnd(startEphemeralRange.startPosition(), endEphemeralRange.startP osition());
302 }
303
304 static PassRefPtrWillBeRawPtr<MutableStylePropertySet> copyStyleOrCreateEmpty(co nst StylePropertySet* style)
305 {
306 if (!style)
307 return MutableStylePropertySet::create();
308 return style->mutableCopy();
309 }
310
311 void ApplyStyleCommand::applyRelativeFontStyleChange(EditingStyle* style)
312 {
313 static const float MinimumFontSize = 0.1f;
314
315 if (!style || !style->hasFontSizeDelta())
316 return;
317
318 Position start = startPosition();
319 Position end = endPosition();
320 if (comparePositions(end, start) < 0) {
321 Position swap = start;
322 start = end;
323 end = swap;
324 }
325
326 // Join up any adjacent text nodes.
327 if (start.anchorNode()->isTextNode()) {
328 joinChildTextNodes(start.anchorNode()->parentNode(), start, end);
329 start = startPosition();
330 end = endPosition();
331 }
332
333 if (start.isNull() || end.isNull())
334 return;
335
336 if (end.anchorNode()->isTextNode() && start.anchorNode()->parentNode() != en d.anchorNode()->parentNode()) {
337 joinChildTextNodes(end.anchorNode()->parentNode(), start, end);
338 start = startPosition();
339 end = endPosition();
340 }
341
342 if (start.isNull() || end.isNull())
343 return;
344
345 // Split the start text nodes if needed to apply style.
346 if (isValidCaretPositionInTextNode(start)) {
347 splitTextAtStart(start, end);
348 start = startPosition();
349 end = endPosition();
350 }
351
352 if (isValidCaretPositionInTextNode(end)) {
353 splitTextAtEnd(start, end);
354 start = startPosition();
355 end = endPosition();
356 }
357
358 // Calculate loop end point.
359 // If the end node is before the start node (can only happen if the end node is
360 // an ancestor of the start node), we gather nodes up to the next sibling of the end node
361 Node* beyondEnd;
362 ASSERT(start.anchorNode());
363 ASSERT(end.anchorNode());
364 if (start.anchorNode()->isDescendantOf(end.anchorNode()))
365 beyondEnd = NodeTraversal::nextSkippingChildren(*end.anchorNode());
366 else
367 beyondEnd = NodeTraversal::next(*end.anchorNode());
368
369 start = start.upstream(); // Move upstream to ensure we do not add redundant spans.
370 Node* startNode = start.anchorNode();
371 ASSERT(startNode);
372
373 // Make sure we're not already at the end or the next NodeTraversal::next() will traverse
374 // past it.
375 if (startNode == beyondEnd)
376 return;
377
378 if (startNode->isTextNode() && start.computeOffsetInContainerNode() >= caret MaxOffset(startNode)) {
379 // Move out of text node if range does not include its characters.
380 startNode = NodeTraversal::next(*startNode);
381 if (!startNode)
382 return;
383 }
384
385 // Store away font size before making any changes to the document.
386 // This ensures that changes to one node won't effect another.
387 WillBeHeapHashMap<RawPtrWillBeMember<Node>, float> startingFontSizes;
388 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(* node)) {
389 ASSERT(node);
390 startingFontSizes.set(node, computedFontSize(node));
391 }
392
393 // These spans were added by us. If empty after font size changes, they can be removed.
394 WillBeHeapVector<RefPtrWillBeMember<HTMLElement>> unstyledSpans;
395
396 Node* lastStyledNode = nullptr;
397 for (Node* node = startNode; node != beyondEnd; node = NodeTraversal::next(* node)) {
398 ASSERT(node);
399 RefPtrWillBeRawPtr<HTMLElement> element = nullptr;
400 if (node->isHTMLElement()) {
401 // Only work on fully selected nodes.
402 if (!elementFullySelected(toHTMLElement(*node), start, end))
403 continue;
404 element = toHTMLElement(node);
405 } else if (node->isTextNode() && node->layoutObject() && node->parentNod e() != lastStyledNode) {
406 // Last styled node was not parent node of this text node, but we wi sh to style this
407 // text node. To make this possible, add a style span to surround th is text node.
408 RefPtrWillBeRawPtr<HTMLSpanElement> span = createStyleSpanElement(do cument());
409 surroundNodeRangeWithElement(node, node, span.get());
410 element = span.release();
411 } else {
412 // Only handle HTML elements and text nodes.
413 continue;
414 }
415 lastStyledNode = node;
416
417 RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleOrCre ateEmpty(element->inlineStyle());
418 float currentFontSize = computedFontSize(node);
419 float desiredFontSize = max(MinimumFontSize, startingFontSizes.get(node) + style->fontSizeDelta());
420 RefPtrWillBeRawPtr<CSSValue> value = inlineStyle->getPropertyCSSValue(CS SPropertyFontSize);
421 if (value) {
422 element->removeInlineStyleProperty(CSSPropertyFontSize);
423 currentFontSize = computedFontSize(node);
424 }
425 if (currentFontSize != desiredFontSize) {
426 inlineStyle->setProperty(CSSPropertyFontSize, cssValuePool().createV alue(desiredFontSize, CSSPrimitiveValue::UnitType::Pixels), false);
427 setNodeAttribute(element.get(), styleAttr, AtomicString(inlineStyle- >asText()));
428 }
429 if (inlineStyle->isEmpty()) {
430 removeElementAttribute(element.get(), styleAttr);
431 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element.get()))
432 unstyledSpans.append(element.release());
433 }
434 }
435
436 for (const auto& unstyledSpan : unstyledSpans)
437 removeNodePreservingChildren(unstyledSpan.get());
438 }
439
440 static ContainerNode* dummySpanAncestorForNode(const Node* node)
441 {
442 while (node && (!node->isElementNode() || !isStyleSpanOrSpanWithOnlyStyleAtt ribute(toElement(node))))
443 node = node->parentNode();
444
445 return node ? node->parentNode() : 0;
446 }
447
448 void ApplyStyleCommand::cleanupUnstyledAppleStyleSpans(ContainerNode* dummySpanA ncestor)
449 {
450 if (!dummySpanAncestor)
451 return;
452
453 // Dummy spans are created when text node is split, so that style informatio n
454 // can be propagated, which can result in more splitting. If a dummy span ge ts
455 // cloned/split, the new node is always a sibling of it. Therefore, we scan
456 // all the children of the dummy's parent
457 Node* next;
458 for (Node* node = dummySpanAncestor->firstChild(); node; node = next) {
459 next = node->nextSibling();
460 if (isSpanWithoutAttributesOrUnstyledStyleSpan(node))
461 removeNodePreservingChildren(node);
462 }
463 }
464
465 HTMLElement* ApplyStyleCommand::splitAncestorsWithUnicodeBidi(Node* node, bool b efore, WritingDirection allowedDirection)
466 {
467 // We are allowed to leave the highest ancestor with unicode-bidi unsplit if it is unicode-bidi: embed and direction: allowedDirection.
468 // In that case, we return the unsplit ancestor. Otherwise, we return 0.
469 Element* block = enclosingBlock(node);
470 if (!block)
471 return 0;
472
473 ContainerNode* highestAncestorWithUnicodeBidi = nullptr;
474 ContainerNode* nextHighestAncestorWithUnicodeBidi = nullptr;
475 int highestAncestorUnicodeBidi = 0;
476 for (ContainerNode* n = node->parentNode(); n != block; n = n->parentNode()) {
477 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create (n).get(), CSSPropertyUnicodeBidi);
478 if (unicodeBidi && unicodeBidi != CSSValueNormal) {
479 highestAncestorUnicodeBidi = unicodeBidi;
480 nextHighestAncestorWithUnicodeBidi = highestAncestorWithUnicodeBidi;
481 highestAncestorWithUnicodeBidi = n;
482 }
483 }
484
485 if (!highestAncestorWithUnicodeBidi)
486 return 0;
487
488 HTMLElement* unsplitAncestor = 0;
489
490 WritingDirection highestAncestorDirection;
491 if (allowedDirection != NaturalWritingDirection
492 && highestAncestorUnicodeBidi != CSSValueBidiOverride
493 && highestAncestorWithUnicodeBidi->isHTMLElement()
494 && EditingStyle::create(highestAncestorWithUnicodeBidi, EditingStyle::Al lProperties)->textDirection(highestAncestorDirection)
495 && highestAncestorDirection == allowedDirection) {
496 if (!nextHighestAncestorWithUnicodeBidi)
497 return toHTMLElement(highestAncestorWithUnicodeBidi);
498
499 unsplitAncestor = toHTMLElement(highestAncestorWithUnicodeBidi);
500 highestAncestorWithUnicodeBidi = nextHighestAncestorWithUnicodeBidi;
501 }
502
503 // Split every ancestor through highest ancestor with embedding.
504 RefPtrWillBeRawPtr<Node> currentNode = node;
505 while (currentNode) {
506 RefPtrWillBeRawPtr<Element> parent = toElement(currentNode->parentNode() );
507 if (before ? currentNode->previousSibling() : currentNode->nextSibling() )
508 splitElement(parent, before ? currentNode.get() : currentNode->nextS ibling());
509 if (parent == highestAncestorWithUnicodeBidi)
510 break;
511 currentNode = parent;
512 }
513 return unsplitAncestor;
514 }
515
516 void ApplyStyleCommand::removeEmbeddingUpToEnclosingBlock(Node* node, HTMLElemen t* unsplitAncestor)
517 {
518 Element* block = enclosingBlock(node);
519 if (!block)
520 return;
521
522 for (ContainerNode* n = node->parentNode(); n != block && n != unsplitAncest or; n = n->parentNode()) {
523 if (!n->isStyledElement())
524 continue;
525
526 Element* element = toElement(n);
527 int unicodeBidi = getIdentifierValue(CSSComputedStyleDeclaration::create (element).get(), CSSPropertyUnicodeBidi);
528 if (!unicodeBidi || unicodeBidi == CSSValueNormal)
529 continue;
530
531 // FIXME: This code should really consider the mapped attribute 'dir', t he inline style declaration,
532 // and all matching style rules in order to determine how to best set th e unicode-bidi property to 'normal'.
533 // For now, it assumes that if the 'dir' attribute is present, then remo ving it will suffice, and
534 // otherwise it sets the property in the inline style declaration.
535 if (element->hasAttribute(dirAttr)) {
536 // FIXME: If this is a BDO element, we should probably just remove i t if it has no
537 // other attributes, like we (should) do with B and I elements.
538 removeElementAttribute(element, dirAttr);
539 } else {
540 RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleO rCreateEmpty(element->inlineStyle());
541 inlineStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueNormal);
542 inlineStyle->removeProperty(CSSPropertyDirection);
543 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asTex t()));
544 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
545 removeNodePreservingChildren(element);
546 }
547 }
548 }
549
550 static HTMLElement* highestEmbeddingAncestor(Node* startNode, Node* enclosingNod e)
551 {
552 for (Node* n = startNode; n && n != enclosingNode; n = n->parentNode()) {
553 if (n->isHTMLElement() && getIdentifierValue(CSSComputedStyleDeclaration ::create(n).get(), CSSPropertyUnicodeBidi) == CSSValueEmbed)
554 return toHTMLElement(n);
555 }
556
557 return 0;
558 }
559
560 void ApplyStyleCommand::applyInlineStyle(EditingStyle* style)
561 {
562 RefPtrWillBeRawPtr<ContainerNode> startDummySpanAncestor = nullptr;
563 RefPtrWillBeRawPtr<ContainerNode> endDummySpanAncestor = nullptr;
564
565 // update document layout once before removing styles
566 // so that we avoid the expense of updating before each and every call
567 // to check a computed style
568 document().updateLayoutIgnorePendingStylesheets();
569
570 // adjust to the positions we want to use for applying style
571 Position start = startPosition();
572 Position end = endPosition();
573
574 if (start.isNull() || end.isNull())
575 return;
576
577 if (comparePositions(end, start) < 0) {
578 Position swap = start;
579 start = end;
580 end = swap;
581 }
582
583 // split the start node and containing element if the selection starts insid e of it
584 bool splitStart = isValidCaretPositionInTextNode(start);
585 if (splitStart) {
586 if (shouldSplitTextElement(start.anchorNode()->parentElement(), style))
587 splitTextElementAtStart(start, end);
588 else
589 splitTextAtStart(start, end);
590 start = startPosition();
591 end = endPosition();
592 if (start.isNull() || end.isNull())
593 return;
594 startDummySpanAncestor = dummySpanAncestorForNode(start.anchorNode());
595 }
596
597 // split the end node and containing element if the selection ends inside of it
598 bool splitEnd = isValidCaretPositionInTextNode(end);
599 if (splitEnd) {
600 if (shouldSplitTextElement(end.anchorNode()->parentElement(), style))
601 splitTextElementAtEnd(start, end);
602 else
603 splitTextAtEnd(start, end);
604 start = startPosition();
605 end = endPosition();
606 if (start.isNull() || end.isNull())
607 return;
608 endDummySpanAncestor = dummySpanAncestorForNode(end.anchorNode());
609 }
610
611 // Remove style from the selection.
612 // Use the upstream position of the start for removing style.
613 // This will ensure we remove all traces of the relevant styles from the sel ection
614 // and prevent us from adding redundant ones, as described in:
615 // <rdar://problem/3724344> Bolding and unbolding creates extraneous tags
616 Position removeStart = start.upstream();
617 WritingDirection textDirection = NaturalWritingDirection;
618 bool hasTextDirection = style->textDirection(textDirection);
619 RefPtrWillBeRawPtr<EditingStyle> styleWithoutEmbedding = nullptr;
620 RefPtrWillBeRawPtr<EditingStyle> embeddingStyle = nullptr;
621 if (hasTextDirection) {
622 // Leave alone an ancestor that provides the desired single level embedd ing, if there is one.
623 HTMLElement* startUnsplitAncestor = splitAncestorsWithUnicodeBidi(start. anchorNode(), true, textDirection);
624 HTMLElement* endUnsplitAncestor = splitAncestorsWithUnicodeBidi(end.anch orNode(), false, textDirection);
625 removeEmbeddingUpToEnclosingBlock(start.anchorNode(), startUnsplitAncest or);
626 removeEmbeddingUpToEnclosingBlock(end.anchorNode(), endUnsplitAncestor);
627
628 // Avoid removing the dir attribute and the unicode-bidi and direction p roperties from the unsplit ancestors.
629 Position embeddingRemoveStart = removeStart;
630 if (startUnsplitAncestor && elementFullySelected(*startUnsplitAncestor, removeStart, end))
631 embeddingRemoveStart = positionInParentAfterNode(*startUnsplitAncest or);
632
633 Position embeddingRemoveEnd = end;
634 if (endUnsplitAncestor && elementFullySelected(*endUnsplitAncestor, remo veStart, end))
635 embeddingRemoveEnd = positionInParentBeforeNode(*endUnsplitAncestor) .downstream();
636
637 if (embeddingRemoveEnd != removeStart || embeddingRemoveEnd != end) {
638 styleWithoutEmbedding = style->copy();
639 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDirectio n();
640
641 if (comparePositions(embeddingRemoveStart, embeddingRemoveEnd) <= 0)
642 removeInlineStyle(embeddingStyle.get(), embeddingRemoveStart, em beddingRemoveEnd);
643 }
644 }
645
646 removeInlineStyle(styleWithoutEmbedding ? styleWithoutEmbedding.get() : styl e, removeStart, end);
647 start = startPosition();
648 end = endPosition();
649 if (start.isNull() || start.isOrphan() || end.isNull() || end.isOrphan())
650 return;
651
652 if (splitStart && mergeStartWithPreviousIfIdentical(start, end)) {
653 start = startPosition();
654 end = endPosition();
655 }
656
657 if (splitEnd) {
658 mergeEndWithNextIfIdentical(start, end);
659 start = startPosition();
660 end = endPosition();
661 }
662
663 // update document layout once before running the rest of the function
664 // so that we avoid the expense of updating before each and every call
665 // to check a computed style
666 document().updateLayoutIgnorePendingStylesheets();
667
668 RefPtrWillBeRawPtr<EditingStyle> styleToApply = style;
669 if (hasTextDirection) {
670 // Avoid applying the unicode-bidi and direction properties beneath ance stors that already have them.
671 HTMLElement* embeddingStartElement = highestEmbeddingAncestor(start.anch orNode(), enclosingBlock(start.anchorNode()));
672 HTMLElement* embeddingEndElement = highestEmbeddingAncestor(end.anchorNo de(), enclosingBlock(end.anchorNode()));
673
674 if (embeddingStartElement || embeddingEndElement) {
675 Position embeddingApplyStart = embeddingStartElement ? positionInPar entAfterNode(*embeddingStartElement) : start;
676 Position embeddingApplyEnd = embeddingEndElement ? positionInParentB eforeNode(*embeddingEndElement) : end;
677 ASSERT(embeddingApplyStart.isNotNull() && embeddingApplyEnd.isNotNul l());
678
679 if (!embeddingStyle) {
680 styleWithoutEmbedding = style->copy();
681 embeddingStyle = styleWithoutEmbedding->extractAndRemoveTextDire ction();
682 }
683 fixRangeAndApplyInlineStyle(embeddingStyle.get(), embeddingApplyStar t, embeddingApplyEnd);
684
685 styleToApply = styleWithoutEmbedding;
686 }
687 }
688
689 fixRangeAndApplyInlineStyle(styleToApply.get(), start, end);
690
691 // Remove dummy style spans created by splitting text elements.
692 cleanupUnstyledAppleStyleSpans(startDummySpanAncestor.get());
693 if (endDummySpanAncestor != startDummySpanAncestor)
694 cleanupUnstyledAppleStyleSpans(endDummySpanAncestor.get());
695 }
696
697 void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const P osition& start, const Position& end)
698 {
699 Node* startNode = start.anchorNode();
700 ASSERT(startNode);
701
702 if (start.computeEditingOffset() >= caretMaxOffset(start.anchorNode())) {
703 startNode = NodeTraversal::next(*startNode);
704 if (!startNode || comparePositions(end, firstPositionInOrBeforeNode(star tNode)) < 0)
705 return;
706 }
707
708 Node* pastEndNode = end.anchorNode();
709 if (end.computeEditingOffset() >= caretMaxOffset(end.anchorNode()))
710 pastEndNode = NodeTraversal::nextSkippingChildren(*end.anchorNode());
711
712 // FIXME: Callers should perform this operation on a Range that includes the br
713 // if they want style applied to the empty line.
714 if (start == end && isHTMLBRElement(*start.anchorNode()))
715 pastEndNode = NodeTraversal::next(*start.anchorNode());
716
717 // Start from the highest fully selected ancestor so that we can modify the fully selected node.
718 // e.g. When applying font-size: large on <font color="blue">hello</font>, w e need to include the font element in our run
719 // to generate <font color="blue" size="4">hello</font> instead of <font col or="blue"><font size="4">hello</font></font>
720 RefPtrWillBeRawPtr<Range> range = Range::create(startNode->document(), start , end);
721 Element* editableRoot = startNode->rootEditableElement();
722 if (startNode != editableRoot) {
723 while (editableRoot && startNode->parentNode() != editableRoot && isNode VisiblyContainedWithin(*startNode->parentNode(), *range))
724 startNode = startNode->parentNode();
725 }
726
727 applyInlineStyleToNodeRange(style, startNode, pastEndNode);
728 }
729
730 static bool containsNonEditableRegion(Node& node)
731 {
732 if (!node.hasEditableStyle())
733 return true;
734
735 Node* sibling = NodeTraversal::nextSkippingChildren(node);
736 for (Node* descendent = node.firstChild(); descendent && descendent != sibli ng; descendent = NodeTraversal::next(*descendent)) {
737 if (!descendent->hasEditableStyle())
738 return true;
739 }
740
741 return false;
742 }
743
744 class InlineRunToApplyStyle {
745 ALLOW_ONLY_INLINE_ALLOCATION();
746 public:
747 InlineRunToApplyStyle(Node* start, Node* end, Node* pastEndNode)
748 : start(start)
749 , end(end)
750 , pastEndNode(pastEndNode)
751 {
752 ASSERT(start->parentNode() == end->parentNode());
753 }
754
755 bool startAndEndAreStillInDocument()
756 {
757 return start && end && start->inDocument() && end->inDocument();
758 }
759
760 DEFINE_INLINE_TRACE()
761 {
762 visitor->trace(start);
763 visitor->trace(end);
764 visitor->trace(pastEndNode);
765 visitor->trace(positionForStyleComputation);
766 visitor->trace(dummyElement);
767 }
768
769 RefPtrWillBeMember<Node> start;
770 RefPtrWillBeMember<Node> end;
771 RefPtrWillBeMember<Node> pastEndNode;
772 Position positionForStyleComputation;
773 RefPtrWillBeMember<HTMLSpanElement> dummyElement;
774 StyleChange change;
775 };
776
777 } // namespace blink
778
779 WTF_ALLOW_INIT_WITH_MEM_FUNCTIONS(blink::InlineRunToApplyStyle);
780
781 namespace blink {
782
783 void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, PassRef PtrWillBeRawPtr<Node> startNode, PassRefPtrWillBeRawPtr<Node> pastEndNode)
784 {
785 if (m_removeOnly)
786 return;
787
788 document().updateLayoutIgnorePendingStylesheets();
789
790 WillBeHeapVector<InlineRunToApplyStyle> runs;
791 RefPtrWillBeRawPtr<Node> node = startNode;
792 for (RefPtrWillBeRawPtr<Node> next; node && node != pastEndNode; node = next ) {
793 next = NodeTraversal::next(*node);
794
795 if (!node->layoutObject() || !node->hasEditableStyle())
796 continue;
797
798 if (!node->layoutObjectIsRichlyEditable() && node->isHTMLElement()) {
799 HTMLElement* element = toHTMLElement(node);
800 // This is a plaintext-only region. Only proceed if it's fully selec ted.
801 // pastEndNode is the node after the last fully selected node, so if it's inside node then
802 // node isn't fully selected.
803 if (pastEndNode && pastEndNode->isDescendantOf(element))
804 break;
805 // Add to this element's inline style and skip over its contents.
806 next = NodeTraversal::nextSkippingChildren(*node);
807 if (!style->style())
808 continue;
809 RefPtrWillBeRawPtr<MutableStylePropertySet> inlineStyle = copyStyleO rCreateEmpty(element->inlineStyle());
810 inlineStyle->mergeAndOverrideOnConflict(style->style());
811 setNodeAttribute(element, styleAttr, AtomicString(inlineStyle->asTex t()));
812 continue;
813 }
814
815 if (isBlock(node.get()))
816 continue;
817
818 if (node->hasChildren()) {
819 if (node->contains(pastEndNode.get()) || containsNonEditableRegion(* node) || !node->parentNode()->hasEditableStyle())
820 continue;
821 if (editingIgnoresContent(node.get())) {
822 next = NodeTraversal::nextSkippingChildren(*node);
823 continue;
824 }
825 }
826
827 Node* runStart = node.get();
828 Node* runEnd = node.get();
829 Node* sibling = node->nextSibling();
830 while (sibling && sibling != pastEndNode && !sibling->contains(pastEndNo de.get())
831 && (!isBlock(sibling) || isHTMLBRElement(*sibling))
832 && !containsNonEditableRegion(*sibling)) {
833 runEnd = sibling;
834 sibling = runEnd->nextSibling();
835 }
836 ASSERT(runEnd);
837 next = NodeTraversal::nextSkippingChildren(*runEnd);
838
839 Node* pastEndNode = NodeTraversal::nextSkippingChildren(*runEnd);
840 if (!shouldApplyInlineStyleToRun(style, runStart, pastEndNode))
841 continue;
842
843 runs.append(InlineRunToApplyStyle(runStart, runEnd, pastEndNode));
844 }
845
846 for (auto& run : runs) {
847 removeConflictingInlineStyleFromRun(style, run.start, run.end, run.pastE ndNode);
848 if (run.startAndEndAreStillInDocument())
849 run.positionForStyleComputation = positionToComputeInlineStyleChange (run.start, run.dummyElement);
850 }
851
852 document().updateLayoutIgnorePendingStylesheets();
853
854 for (auto& run : runs) {
855 if (run.positionForStyleComputation.isNotNull())
856 run.change = StyleChange(style, run.positionForStyleComputation);
857 }
858
859 for (auto& run : runs) {
860 if (run.dummyElement)
861 removeNode(run.dummyElement);
862 if (run.startAndEndAreStillInDocument())
863 applyInlineStyleChange(run.start.release(), run.end.release(), run.c hange, AddStyledElement);
864 }
865 }
866
867 bool ApplyStyleCommand::isStyledInlineElementToRemove(Element* element) const
868 {
869 return (m_styledInlineElement && element->hasTagName(m_styledInlineElement-> tagQName()))
870 || (m_isInlineElementToRemoveFunction && m_isInlineElementToRemoveFuncti on(element));
871 }
872
873 bool ApplyStyleCommand::shouldApplyInlineStyleToRun(EditingStyle* style, Node* r unStart, Node* pastEndNode)
874 {
875 ASSERT(style && runStart);
876
877 for (Node* node = runStart; node && node != pastEndNode; node = NodeTraversa l::next(*node)) {
878 if (node->hasChildren())
879 continue;
880 // We don't consider m_isInlineElementToRemoveFunction here because we n ever apply style when m_isInlineElementToRemoveFunction is specified
881 if (!style->styleIsPresentInComputedStyleOfNode(node))
882 return true;
883 if (m_styledInlineElement && !enclosingElementWithTag(positionBeforeNode (node), m_styledInlineElement->tagQName()))
884 return true;
885 }
886 return false;
887 }
888
889 void ApplyStyleCommand::removeConflictingInlineStyleFromRun(EditingStyle* style, RefPtrWillBeMember<Node>& runStart, RefPtrWillBeMember<Node>& runEnd, PassRefPt rWillBeRawPtr<Node> pastEndNode)
890 {
891 ASSERT(runStart && runEnd);
892 RefPtrWillBeRawPtr<Node> next = runStart;
893 for (RefPtrWillBeRawPtr<Node> node = next; node && node->inDocument() && nod e != pastEndNode; node = next) {
894 if (editingIgnoresContent(node.get())) {
895 ASSERT(!node->contains(pastEndNode.get()));
896 next = NodeTraversal::nextSkippingChildren(*node);
897 } else {
898 next = NodeTraversal::next(*node);
899 }
900 if (!node->isHTMLElement())
901 continue;
902
903 HTMLElement& element = toHTMLElement(*node);
904 RefPtrWillBeRawPtr<Node> previousSibling = element.previousSibling();
905 RefPtrWillBeRawPtr<Node> nextSibling = element.nextSibling();
906 RefPtrWillBeRawPtr<ContainerNode> parent = element.parentNode();
907 removeInlineStyleFromElement(style, &element, RemoveAlways);
908 if (!element.inDocument()) {
909 // FIXME: We might need to update the start and the end of current s election here but need a test.
910 if (runStart == element)
911 runStart = previousSibling ? previousSibling->nextSibling() : pa rent->firstChild();
912 if (runEnd == element)
913 runEnd = nextSibling ? nextSibling->previousSibling() : parent-> lastChild();
914 }
915 }
916 }
917
918 bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRe fPtrWillBeRawPtr<HTMLElement> element, InlineStyleRemovalMode mode, EditingStyle * extractedStyle)
919 {
920 ASSERT(element);
921
922 if (!element->parentNode() || !element->parentNode()->isContentEditable(Node ::UserSelectAllIsAlwaysNonEditable))
923 return false;
924
925 if (isStyledInlineElementToRemove(element.get())) {
926 if (mode == RemoveNone)
927 return true;
928 if (extractedStyle)
929 extractedStyle->mergeInlineStyleOfElement(element.get(), EditingStyl e::OverrideValues);
930 removeNodePreservingChildren(element);
931 return true;
932 }
933
934 bool removed = false;
935 if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle ))
936 removed = true;
937
938 if (!element->inDocument())
939 return removed;
940
941 // If the node was converted to a span, the span may still contain relevant
942 // styles which must be removed (e.g. <b style='font-weight: bold'>)
943 if (removeCSSStyle(style, element.get(), mode, extractedStyle))
944 removed = true;
945
946 return removed;
947 }
948
949 void ApplyStyleCommand::replaceWithSpanOrRemoveIfWithoutAttributes(HTMLElement* elem)
950 {
951 if (hasNoAttributeOrOnlyStyleAttribute(elem, StyleAttributeShouldBeEmpty))
952 removeNodePreservingChildren(elem);
953 else
954 replaceElementWithSpanPreservingChildrenAndAttributes(elem);
955 }
956
957 bool ApplyStyleCommand::removeImplicitlyStyledElement(EditingStyle* style, HTMLE lement* element, InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
958 {
959 ASSERT(style);
960 if (mode == RemoveNone) {
961 ASSERT(!extractedStyle);
962 return style->conflictsWithImplicitStyleOfElement(element) || style->con flictsWithImplicitStyleOfAttributes(element);
963 }
964
965 ASSERT(mode == RemoveIfNeeded || mode == RemoveAlways);
966 if (style->conflictsWithImplicitStyleOfElement(element, extractedStyle, mode == RemoveAlways ? EditingStyle::ExtractMatchingStyle : EditingStyle::DoNotExtra ctMatchingStyle)) {
967 replaceWithSpanOrRemoveIfWithoutAttributes(element);
968 return true;
969 }
970
971 // unicode-bidi and direction are pushed down separately so don't push down with other styles
972 Vector<QualifiedName> attributes;
973 if (!style->extractConflictingImplicitStyleOfAttributes(element, extractedSt yle ? EditingStyle::PreserveWritingDirection : EditingStyle::DoNotPreserveWritin gDirection,
974 extractedStyle, attributes, mode == RemoveAlways ? EditingStyle::Extract MatchingStyle : EditingStyle::DoNotExtractMatchingStyle))
975 return false;
976
977 for (const auto& attribute : attributes)
978 removeElementAttribute(element, attribute);
979
980 if (isEmptyFontTag(element) || isSpanWithoutAttributesOrUnstyledStyleSpan(el ement))
981 removeNodePreservingChildren(element);
982
983 return true;
984 }
985
986 bool ApplyStyleCommand::removeCSSStyle(EditingStyle* style, HTMLElement* element , InlineStyleRemovalMode mode, EditingStyle* extractedStyle)
987 {
988 ASSERT(style);
989 ASSERT(element);
990
991 if (mode == RemoveNone)
992 return style->conflictsWithInlineStyleOfElement(element);
993
994 Vector<CSSPropertyID> properties;
995 if (!style->conflictsWithInlineStyleOfElement(element, extractedStyle, prope rties))
996 return false;
997
998 // FIXME: We should use a mass-removal function here but we don't have an un doable one yet.
999 for (const auto& property : properties)
1000 removeCSSProperty(element, property);
1001
1002 if (isSpanWithoutAttributesOrUnstyledStyleSpan(element))
1003 removeNodePreservingChildren(element);
1004
1005 return true;
1006 }
1007
1008 HTMLElement* ApplyStyleCommand::highestAncestorWithConflictingInlineStyle(Editin gStyle* style, Node* node)
1009 {
1010 if (!node)
1011 return 0;
1012
1013 HTMLElement* result = nullptr;
1014 Node* unsplittableElement = unsplittableElementForPosition(firstPositionInOr BeforeNode(node));
1015
1016 for (Node *n = node; n; n = n->parentNode()) {
1017 if (n->isHTMLElement() && shouldRemoveInlineStyleFromElement(style, toHT MLElement(n)))
1018 result = toHTMLElement(n);
1019 // Should stop at the editable root (cannot cross editing boundary) and
1020 // also stop at the unsplittable element to be consistent with other UAs
1021 if (n == unsplittableElement)
1022 break;
1023 }
1024
1025 return result;
1026 }
1027
1028 void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* sty le)
1029 {
1030 ASSERT(node);
1031
1032 node->document().updateLayoutTreeIfNeeded();
1033
1034 if (!style || style->isEmpty() || !node->layoutObject() || isHTMLIFrameEleme nt(*node))
1035 return;
1036
1037 RefPtrWillBeRawPtr<EditingStyle> newInlineStyle = style;
1038 if (node->isHTMLElement() && toHTMLElement(node)->inlineStyle()) {
1039 newInlineStyle = style->copy();
1040 newInlineStyle->mergeInlineStyleOfElement(toHTMLElement(node), EditingSt yle::OverrideValues);
1041 }
1042
1043 // Since addInlineStyleIfNeeded can't add styles to block-flow layout object s, add style attribute instead.
1044 // FIXME: applyInlineStyleToRange should be used here instead.
1045 if ((node->layoutObject()->isLayoutBlockFlow() || node->hasChildren()) && no de->isHTMLElement()) {
1046 setNodeAttribute(toHTMLElement(node), styleAttr, AtomicString(newInlineS tyle->style()->asText()));
1047 return;
1048 }
1049
1050 if (node->layoutObject()->isText() && toLayoutText(node->layoutObject())->is AllCollapsibleWhitespace())
1051 return;
1052
1053 // We can't wrap node with the styled element here because new styled elemen t will never be removed if we did.
1054 // If we modified the child pointer in pushDownInlineStyleAroundNode to poin t to new style element
1055 // then we fall into an infinite loop where we keep removing and adding styl ed element wrapping node.
1056 addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledEleme nt);
1057 }
1058
1059 void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode)
1060 {
1061 HTMLElement* highestAncestor = highestAncestorWithConflictingInlineStyle(sty le, targetNode);
1062 if (!highestAncestor)
1063 return;
1064
1065 // The outer loop is traversing the tree vertically from highestAncestor to targetNode
1066 RefPtrWillBeRawPtr<Node> current = highestAncestor;
1067 // Along the way, styled elements that contain targetNode are removed and ac cumulated into elementsToPushDown.
1068 // Each child of the removed element, exclusing ancestors of targetNode, is then wrapped by clones of elements in elementsToPushDown.
1069 WillBeHeapVector<RefPtrWillBeMember<Element>> elementsToPushDown;
1070 while (current && current != targetNode && current->contains(targetNode)) {
1071 NodeVector currentChildren;
1072 getChildNodes(toContainerNode(*current), currentChildren);
1073 RefPtrWillBeRawPtr<Element> styledElement = nullptr;
1074 if (current->isStyledElement() && isStyledInlineElementToRemove(toElemen t(current))) {
1075 styledElement = toElement(current);
1076 elementsToPushDown.append(styledElement);
1077 }
1078
1079 RefPtrWillBeRawPtr<EditingStyle> styleToPushDown = EditingStyle::create( );
1080 if (current->isHTMLElement())
1081 removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIf Needed, styleToPushDown.get());
1082
1083 // The inner loop will go through children on each level
1084 // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately.
1085 for (const auto& currentChild : currentChildren) {
1086 Node* child = currentChild.get();
1087 if (!child->parentNode())
1088 continue;
1089 if (!child->contains(targetNode) && elementsToPushDown.size()) {
1090 for (const auto& element : elementsToPushDown) {
1091 RefPtrWillBeRawPtr<Element> wrapper = element->cloneElementW ithoutChildren();
1092 wrapper->removeAttribute(styleAttr);
1093 // Delete id attribute from the second element because the s ame id cannot be used for more than one element
1094 element->removeAttribute(HTMLNames::idAttr);
1095 if (isHTMLAnchorElement(element))
1096 element->removeAttribute(HTMLNames::nameAttr);
1097 surroundNodeRangeWithElement(child, child, wrapper);
1098 }
1099 }
1100
1101 // Apply style to all nodes containing targetNode and their siblings but NOT to targetNode
1102 // But if we've removed styledElement then go ahead and always apply the style.
1103 if (child != targetNode || styledElement)
1104 applyInlineStyleToPushDown(child, styleToPushDown.get());
1105
1106 // We found the next node for the outer loop (contains targetNode)
1107 // When reached targetNode, stop the outer loop upon the completion of the current inner loop
1108 if (child == targetNode || child->contains(targetNode))
1109 current = child;
1110 }
1111 }
1112 }
1113
1114 void ApplyStyleCommand::removeInlineStyle(EditingStyle* style, const Position &s tart, const Position &end)
1115 {
1116 ASSERT(start.isNotNull());
1117 ASSERT(end.isNotNull());
1118 ASSERT(start.inDocument());
1119 ASSERT(end.inDocument());
1120 ASSERT(Position::commonAncestorTreeScope(start, end));
1121 ASSERT(comparePositions(start, end) <= 0);
1122 // FIXME: We should assert that start/end are not in the middle of a text no de.
1123
1124 Position pushDownStart = start.downstream();
1125 // If the pushDownStart is at the end of a text node, then this node is not fully selected.
1126 // Move it to the next deep quivalent position to avoid removing the style f rom this node.
1127 // e.g. if pushDownStart was at Position("hello", 5) in <b>hello<div>world</ div></b>, we want Position("world", 0) instead.
1128 Node* pushDownStartContainer = pushDownStart.computeContainerNode();
1129 if (pushDownStartContainer && pushDownStartContainer->isTextNode()
1130 && pushDownStart.computeOffsetInContainerNode() == pushDownStartContaine r->maxCharacterOffset())
1131 pushDownStart = nextVisuallyDistinctCandidate(pushDownStart);
1132 Position pushDownEnd = end.upstream();
1133 // If pushDownEnd is at the start of a text node, then this node is not full y selected.
1134 // Move it to the previous deep equivalent position to avoid removing the st yle from this node.
1135 Node* pushDownEndContainer = pushDownEnd.computeContainerNode();
1136 if (pushDownEndContainer && pushDownEndContainer->isTextNode() && !pushDownE nd.computeOffsetInContainerNode())
1137 pushDownEnd = previousVisuallyDistinctCandidate(pushDownEnd);
1138
1139 pushDownInlineStyleAroundNode(style, pushDownStart.anchorNode());
1140 pushDownInlineStyleAroundNode(style, pushDownEnd.anchorNode());
1141
1142 // The s and e variables store the positions used to set the ending selectio n after style removal
1143 // takes place. This will help callers to recognize when either the start no de or the end node
1144 // are removed from the document during the work of this function.
1145 // If pushDownInlineStyleAroundNode has pruned start.anchorNode() or end.anc horNode(),
1146 // use pushDownStart or pushDownEnd instead, which pushDownInlineStyleAround Node won't prune.
1147 Position s = start.isNull() || start.isOrphan() ? pushDownStart : start;
1148 Position e = end.isNull() || end.isOrphan() ? pushDownEnd : end;
1149
1150 // Current ending selection resetting algorithm assumes |start| and |end|
1151 // are in a same DOM tree even if they are not in document.
1152 if (!Position::commonAncestorTreeScope(start, end))
1153 return;
1154
1155 RefPtrWillBeRawPtr<Node> node = start.anchorNode();
1156 while (node) {
1157 RefPtrWillBeRawPtr<Node> next = nullptr;
1158 if (editingIgnoresContent(node.get())) {
1159 ASSERT(node == end.anchorNode() || !node->contains(end.anchorNode()) );
1160 next = NodeTraversal::nextSkippingChildren(*node);
1161 } else {
1162 next = NodeTraversal::next(*node);
1163 }
1164 if (node->isHTMLElement() && elementFullySelected(toHTMLElement(*node), start, end)) {
1165 RefPtrWillBeRawPtr<HTMLElement> elem = toHTMLElement(node);
1166 RefPtrWillBeRawPtr<Node> prev = NodeTraversal::previousPostOrder(*el em);
1167 RefPtrWillBeRawPtr<Node> next = NodeTraversal::next(*elem);
1168 RefPtrWillBeRawPtr<EditingStyle> styleToPushDown = nullptr;
1169 RefPtrWillBeRawPtr<Node> childNode = nullptr;
1170 if (isStyledInlineElementToRemove(elem.get())) {
1171 styleToPushDown = EditingStyle::create();
1172 childNode = elem->firstChild();
1173 }
1174
1175 removeInlineStyleFromElement(style, elem.get(), RemoveIfNeeded, styl eToPushDown.get());
1176 if (!elem->inDocument()) {
1177 if (s.anchorNode() == elem) {
1178 // Since elem must have been fully selected, and it is at th e start
1179 // of the selection, it is clear we can set the new s offset to 0.
1180 ASSERT(s.isBeforeAnchor() || s.isBeforeChildren() || s.offse tInContainerNode() <= 0);
1181 s = firstPositionInOrBeforeNode(next.get());
1182 }
1183 if (e.anchorNode() == elem) {
1184 // Since elem must have been fully selected, and it is at th e end
1185 // of the selection, it is clear we can set the new e offset to
1186 // the max range offset of prev.
1187 ASSERT(s.isAfterAnchor() || !offsetIsBeforeLastNodeOffset(s. offsetInContainerNode(), s.computeContainerNode()));
1188 e = lastPositionInOrAfterNode(prev.get());
1189 }
1190 }
1191
1192 if (styleToPushDown) {
1193 for (; childNode; childNode = childNode->nextSibling())
1194 applyInlineStyleToPushDown(childNode.get(), styleToPushDown. get());
1195 }
1196 }
1197 if (node == end.anchorNode())
1198 break;
1199 node = next;
1200 }
1201
1202 updateStartEnd(s, e);
1203 }
1204
1205 bool ApplyStyleCommand::elementFullySelected(HTMLElement& element, const Positio n& start, const Position& end) const
1206 {
1207 // The tree may have changed and Position::upstream() relies on an up-to-dat e layout.
1208 element.document().updateLayoutIgnorePendingStylesheets();
1209
1210 return comparePositions(firstPositionInOrBeforeNode(&element), start) >= 0
1211 && comparePositions(lastPositionInOrAfterNode(&element).upstream(), end) <= 0;
1212 }
1213
1214 void ApplyStyleCommand::splitTextAtStart(const Position& start, const Position& end)
1215 {
1216 ASSERT(start.computeContainerNode()->isTextNode());
1217
1218 Position newEnd;
1219 if (end.isOffsetInAnchor() && start.computeContainerNode() == end.computeCon tainerNode())
1220 newEnd = Position(end.computeContainerNode(), end.offsetInContainerNode( ) - start.offsetInContainerNode());
1221 else
1222 newEnd = end;
1223
1224 RefPtrWillBeRawPtr<Text> text = toText(start.computeContainerNode());
1225 splitTextNode(text, start.offsetInContainerNode());
1226 updateStartEnd(firstPositionInNode(text.get()), newEnd);
1227 }
1228
1229 void ApplyStyleCommand::splitTextAtEnd(const Position& start, const Position& en d)
1230 {
1231 ASSERT(end.computeContainerNode()->isTextNode());
1232
1233 bool shouldUpdateStart = start.isOffsetInAnchor() && start.computeContainerN ode() == end.computeContainerNode();
1234 Text* text = toText(end.anchorNode());
1235 splitTextNode(text, end.offsetInContainerNode());
1236
1237 Node* prevNode = text->previousSibling();
1238 if (!prevNode || !prevNode->isTextNode())
1239 return;
1240
1241 Position newStart = shouldUpdateStart ? Position(toText(prevNode), start.off setInContainerNode()) : start;
1242 updateStartEnd(newStart, lastPositionInNode(prevNode));
1243 }
1244
1245 void ApplyStyleCommand::splitTextElementAtStart(const Position& start, const Pos ition& end)
1246 {
1247 ASSERT(start.computeContainerNode()->isTextNode());
1248
1249 Position newEnd;
1250 if (start.computeContainerNode() == end.computeContainerNode())
1251 newEnd = Position(end.computeContainerNode(), end.offsetInContainerNode( ) - start.offsetInContainerNode());
1252 else
1253 newEnd = end;
1254
1255 splitTextNodeContainingElement(toText(start.computeContainerNode()), start.o ffsetInContainerNode());
1256 updateStartEnd(positionBeforeNode(start.computeContainerNode()), newEnd);
1257 }
1258
1259 void ApplyStyleCommand::splitTextElementAtEnd(const Position& start, const Posit ion& end)
1260 {
1261 ASSERT(end.computeContainerNode()->isTextNode());
1262
1263 bool shouldUpdateStart = start.computeContainerNode() == end.computeContaine rNode();
1264 splitTextNodeContainingElement(toText(end.computeContainerNode()), end.offse tInContainerNode());
1265
1266 Node* parentElement = end.computeContainerNode()->parentNode();
1267 if (!parentElement || !parentElement->previousSibling())
1268 return;
1269 Node* firstTextNode = parentElement->previousSibling()->lastChild();
1270 if (!firstTextNode || !firstTextNode->isTextNode())
1271 return;
1272
1273 Position newStart = shouldUpdateStart ? Position(toText(firstTextNode), star t.offsetInContainerNode()) : start;
1274 updateStartEnd(newStart, positionAfterNode(firstTextNode));
1275 }
1276
1277 bool ApplyStyleCommand::shouldSplitTextElement(Element* element, EditingStyle* s tyle)
1278 {
1279 if (!element || !element->isHTMLElement())
1280 return false;
1281
1282 return shouldRemoveInlineStyleFromElement(style, toHTMLElement(element));
1283 }
1284
1285 bool ApplyStyleCommand::isValidCaretPositionInTextNode(const Position& position)
1286 {
1287 ASSERT(position.isNotNull());
1288
1289 Node* node = position.computeContainerNode();
1290 if (!position.isOffsetInAnchor() || !node->isTextNode())
1291 return false;
1292 int offsetInText = position.offsetInContainerNode();
1293 return offsetInText > caretMinOffset(node) && offsetInText < caretMaxOffset( node);
1294 }
1295
1296 bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position& start, const Position& end)
1297 {
1298 Node* startNode = start.computeContainerNode();
1299 int startOffset = start.computeOffsetInContainerNode();
1300 if (startOffset)
1301 return false;
1302
1303 if (isAtomicNode(startNode)) {
1304 // note: prior siblings could be unrendered elements. it's silly to miss the
1305 // merge opportunity just for that.
1306 if (startNode->previousSibling())
1307 return false;
1308
1309 startNode = startNode->parentNode();
1310 }
1311
1312 if (!startNode->isElementNode())
1313 return false;
1314
1315 Node* previousSibling = startNode->previousSibling();
1316
1317 if (previousSibling && areIdenticalElements(startNode, previousSibling)) {
1318 Element* previousElement = toElement(previousSibling);
1319 Element* element = toElement(startNode);
1320 Node* startChild = element->firstChild();
1321 ASSERT(startChild);
1322 mergeIdenticalElements(previousElement, element);
1323
1324 int startOffsetAdjustment = startChild->nodeIndex();
1325 int endOffsetAdjustment = startNode == end.anchorNode() ? startOffsetAdj ustment : 0;
1326 updateStartEnd(Position(startNode, startOffsetAdjustment),
1327 Position(end.anchorNode(), end.computeEditingOffset() + endOffsetAdj ustment));
1328 return true;
1329 }
1330
1331 return false;
1332 }
1333
1334 bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position& start, const Position& end)
1335 {
1336 Node* endNode = end.computeContainerNode();
1337
1338 if (isAtomicNode(endNode)) {
1339 int endOffset = end.computeOffsetInContainerNode();
1340 if (offsetIsBeforeLastNodeOffset(endOffset, endNode))
1341 return false;
1342
1343 if (end.anchorNode()->nextSibling())
1344 return false;
1345
1346 endNode = end.anchorNode()->parentNode();
1347 }
1348
1349 if (!endNode->isElementNode() || isHTMLBRElement(*endNode))
1350 return false;
1351
1352 Node* nextSibling = endNode->nextSibling();
1353 if (nextSibling && areIdenticalElements(endNode, nextSibling)) {
1354 Element* nextElement = toElement(nextSibling);
1355 Element* element = toElement(endNode);
1356 Node* nextChild = nextElement->firstChild();
1357
1358 mergeIdenticalElements(element, nextElement);
1359
1360 bool shouldUpdateStart = start.computeContainerNode() == endNode;
1361 int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childN odes()->length();
1362 updateStartEnd(shouldUpdateStart ? Position(nextElement, start.offsetInC ontainerNode()) : start,
1363 Position(nextElement, endOffset));
1364 return true;
1365 }
1366
1367 return false;
1368 }
1369
1370 void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtrWillBeRawPtr<Node > passedStartNode, PassRefPtrWillBeRawPtr<Node> endNode, PassRefPtrWillBeRawPtr< Element> elementToInsert)
1371 {
1372 ASSERT(passedStartNode);
1373 ASSERT(endNode);
1374 ASSERT(elementToInsert);
1375 RefPtrWillBeRawPtr<Node> node = passedStartNode;
1376 RefPtrWillBeRawPtr<Element> element = elementToInsert;
1377
1378 insertNodeBefore(element, node);
1379
1380 while (node) {
1381 RefPtrWillBeRawPtr<Node> next = node->nextSibling();
1382 if (node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable)) {
1383 removeNode(node);
1384 appendNode(node, element);
1385 }
1386 if (node == endNode)
1387 break;
1388 node = next;
1389 }
1390
1391 RefPtrWillBeRawPtr<Node> nextSibling = element->nextSibling();
1392 RefPtrWillBeRawPtr<Node> previousSibling = element->previousSibling();
1393 if (nextSibling && nextSibling->isElementNode() && nextSibling->hasEditableS tyle()
1394 && areIdenticalElements(element.get(), toElement(nextSibling)))
1395 mergeIdenticalElements(element.get(), toElement(nextSibling));
1396
1397 if (previousSibling && previousSibling->isElementNode() && previousSibling-> hasEditableStyle()) {
1398 Node* mergedElement = previousSibling->nextSibling();
1399 if (mergedElement->isElementNode() && mergedElement->hasEditableStyle()
1400 && areIdenticalElements(toElement(previousSibling), toElement(merged Element)))
1401 mergeIdenticalElements(toElement(previousSibling), toElement(mergedE lement));
1402 }
1403
1404 // FIXME: We should probably call updateStartEnd if the start or end was in the node
1405 // range so that the endingSelection() is canonicalized. See the comments a t the end of
1406 // VisibleSelection::validate().
1407 }
1408
1409 void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElemen t* block)
1410 {
1411 // Do not check for legacy styles here. Those styles, like <B> and <I>, only apply for
1412 // inline content.
1413 if (!block)
1414 return;
1415
1416 String cssStyle = styleChange.cssStyle();
1417 StringBuilder cssText;
1418 cssText.append(cssStyle);
1419 if (const StylePropertySet* decl = block->inlineStyle()) {
1420 if (!cssStyle.isEmpty())
1421 cssText.append(' ');
1422 cssText.append(decl->asText());
1423 }
1424 setNodeAttribute(block, styleAttr, cssText.toAtomicString());
1425 }
1426
1427 void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtrWi llBeRawPtr<Node> passedStart, PassRefPtrWillBeRawPtr<Node> passedEnd, EAddStyled Element addStyledElement)
1428 {
1429 if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd-> inDocument())
1430 return;
1431
1432 RefPtrWillBeRawPtr<Node> start = passedStart;
1433 RefPtrWillBeMember<HTMLSpanElement> dummyElement = nullptr;
1434 StyleChange styleChange(style, positionToComputeInlineStyleChange(start, dum myElement));
1435
1436 if (dummyElement)
1437 removeNode(dummyElement);
1438
1439 applyInlineStyleChange(start, passedEnd, styleChange, addStyledElement);
1440 }
1441
1442 Position ApplyStyleCommand::positionToComputeInlineStyleChange(PassRefPtrWillBeR awPtr<Node> startNode, RefPtrWillBeMember<HTMLSpanElement>& dummyElement)
1443 {
1444 // It's okay to obtain the style at the startNode because we've removed all relevant styles from the current run.
1445 if (!startNode->isElementNode()) {
1446 dummyElement = createStyleSpanElement(document());
1447 insertNodeAt(dummyElement, positionBeforeNode(startNode.get()));
1448 return positionBeforeNode(dummyElement.get());
1449 }
1450
1451 return firstPositionInOrBeforeNode(startNode.get());
1452 }
1453
1454 void ApplyStyleCommand::applyInlineStyleChange(PassRefPtrWillBeRawPtr<Node> pass edStart, PassRefPtrWillBeRawPtr<Node> passedEnd, StyleChange& styleChange, EAddS tyledElement addStyledElement)
1455 {
1456 RefPtrWillBeRawPtr<Node> startNode = passedStart;
1457 RefPtrWillBeRawPtr<Node> endNode = passedEnd;
1458 ASSERT(startNode->inDocument());
1459 ASSERT(endNode->inDocument());
1460
1461 // Find appropriate font and span elements top-down.
1462 HTMLFontElement* fontContainer = nullptr;
1463 HTMLElement* styleContainer = nullptr;
1464 for (Node* container = startNode.get(); container && startNode == endNode; c ontainer = container->firstChild()) {
1465 if (isHTMLFontElement(*container))
1466 fontContainer = toHTMLFontElement(container);
1467 bool styleContainerIsNotSpan = !isHTMLSpanElement(styleContainer);
1468 if (container->isHTMLElement()) {
1469 HTMLElement* containerElement = toHTMLElement(container);
1470 if (isHTMLSpanElement(*containerElement) || (styleContainerIsNotSpan && containerElement->hasChildren()))
1471 styleContainer = toHTMLElement(container);
1472 }
1473 if (!container->hasChildren())
1474 break;
1475 startNode = container->firstChild();
1476 endNode = container->lastChild();
1477 }
1478
1479 // Font tags need to go outside of CSS so that CSS font sizes override leagc y font sizes.
1480 if (styleChange.applyFontColor() || styleChange.applyFontFace() || styleChan ge.applyFontSize()) {
1481 if (fontContainer) {
1482 if (styleChange.applyFontColor())
1483 setNodeAttribute(fontContainer, colorAttr, AtomicString(styleCha nge.fontColor()));
1484 if (styleChange.applyFontFace())
1485 setNodeAttribute(fontContainer, faceAttr, AtomicString(styleChan ge.fontFace()));
1486 if (styleChange.applyFontSize())
1487 setNodeAttribute(fontContainer, sizeAttr, AtomicString(styleChan ge.fontSize()));
1488 } else {
1489 RefPtrWillBeRawPtr<HTMLFontElement> fontElement = createFontElement( document());
1490 if (styleChange.applyFontColor())
1491 fontElement->setAttribute(colorAttr, AtomicString(styleChange.fo ntColor()));
1492 if (styleChange.applyFontFace())
1493 fontElement->setAttribute(faceAttr, AtomicString(styleChange.fon tFace()));
1494 if (styleChange.applyFontSize())
1495 fontElement->setAttribute(sizeAttr, AtomicString(styleChange.fon tSize()));
1496 surroundNodeRangeWithElement(startNode, endNode, fontElement.get());
1497 }
1498 }
1499
1500 if (styleChange.cssStyle().length()) {
1501 if (styleContainer) {
1502 if (const StylePropertySet* existingStyle = styleContainer->inlineSt yle()) {
1503 String existingText = existingStyle->asText();
1504 StringBuilder cssText;
1505 cssText.append(existingText);
1506 if (!existingText.isEmpty())
1507 cssText.append(' ');
1508 cssText.append(styleChange.cssStyle());
1509 setNodeAttribute(styleContainer, styleAttr, cssText.toAtomicStri ng());
1510 } else {
1511 setNodeAttribute(styleContainer, styleAttr, AtomicString(styleCh ange.cssStyle()));
1512 }
1513 } else {
1514 RefPtrWillBeRawPtr<HTMLSpanElement> styleElement = createStyleSpanEl ement(document());
1515 styleElement->setAttribute(styleAttr, AtomicString(styleChange.cssSt yle()));
1516 surroundNodeRangeWithElement(startNode, endNode, styleElement.releas e());
1517 }
1518 }
1519
1520 if (styleChange.applyBold())
1521 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(docum ent(), bTag));
1522
1523 if (styleChange.applyItalic())
1524 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(docum ent(), iTag));
1525
1526 if (styleChange.applyUnderline())
1527 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(docum ent(), uTag));
1528
1529 if (styleChange.applyLineThrough())
1530 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(docum ent(), strikeTag));
1531
1532 if (styleChange.applySubscript())
1533 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(docum ent(), subTag));
1534 else if (styleChange.applySuperscript())
1535 surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(docum ent(), supTag));
1536
1537 if (m_styledInlineElement && addStyledElement == AddStyledElement)
1538 surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement-> cloneElementWithoutChildren());
1539 }
1540
1541 float ApplyStyleCommand::computedFontSize(Node* node)
1542 {
1543 if (!node)
1544 return 0;
1545
1546 RefPtrWillBeRawPtr<CSSComputedStyleDeclaration> style = CSSComputedStyleDecl aration::create(node);
1547 if (!style)
1548 return 0;
1549
1550 RefPtrWillBeRawPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimiti veValue>(style->getPropertyCSSValue(CSSPropertyFontSize));
1551 if (!value)
1552 return 0;
1553
1554 ASSERT(value->typeWithCalcResolved() == CSSPrimitiveValue::UnitType::Pixels) ;
1555 return value->getFloatValue();
1556 }
1557
1558 void ApplyStyleCommand::joinChildTextNodes(ContainerNode* node, const Position& start, const Position& end)
1559 {
1560 if (!node)
1561 return;
1562
1563 Position newStart = start;
1564 Position newEnd = end;
1565
1566 WillBeHeapVector<RefPtrWillBeMember<Text>> textNodes;
1567 for (Node* curr = node->firstChild(); curr; curr = curr->nextSibling()) {
1568 if (!curr->isTextNode())
1569 continue;
1570
1571 textNodes.append(toText(curr));
1572 }
1573
1574 for (const auto& textNode : textNodes) {
1575 Text* childText = textNode.get();
1576 Node* next = childText->nextSibling();
1577 if (!next || !next->isTextNode())
1578 continue;
1579
1580 Text* nextText = toText(next);
1581 if (start.isOffsetInAnchor() && next == start.computeContainerNode())
1582 newStart = Position(childText, childText->length() + start.offsetInC ontainerNode());
1583 if (end.isOffsetInAnchor() && next == end.computeContainerNode())
1584 newEnd = Position(childText, childText->length() + end.offsetInConta inerNode());
1585 String textToMove = nextText->data();
1586 insertTextIntoNode(childText, childText->length(), textToMove);
1587 removeNode(next);
1588 // don't move child node pointer. it may want to merge with more text no des.
1589 }
1590
1591 updateStartEnd(newStart, newEnd);
1592 }
1593
1594 DEFINE_TRACE(ApplyStyleCommand)
1595 {
1596 visitor->trace(m_style);
1597 visitor->trace(m_start);
1598 visitor->trace(m_end);
1599 visitor->trace(m_styledInlineElement);
1600 CompositeEditCommand::trace(visitor);
1601 }
1602
1603 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698