OLD | NEW |
| (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 } | |
OLD | NEW |