| 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 |