| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. |
| 4 |
| 5 #include "EditingStyleUtilities.h" |
| 6 |
| 7 #include "core/css/CSSColorValue.h" |
| 8 #include "core/css/CSSComputedStyleDeclaration.h" |
| 9 #include "core/css/CSSIdentifierValue.h" |
| 10 #include "core/css/StylePropertySet.h" |
| 11 #include "core/css/parser/CSSParser.h" |
| 12 #include "core/editing/EditingStyle.h" |
| 13 #include "core/editing/EditingUtilities.h" |
| 14 |
| 15 namespace blink { |
| 16 |
| 17 EditingStyle* EditingStyleUtilities::createWrappingStyleForSerialization( |
| 18 ContainerNode* context) { |
| 19 DCHECK(context); |
| 20 EditingStyle* wrappingStyle = EditingStyle::create(); |
| 21 |
| 22 // When not annotating for interchange, we only preserve inline style |
| 23 // declarations. |
| 24 for (Node& node : NodeTraversal::inclusiveAncestorsOf(*context)) { |
| 25 if (node.isDocumentNode()) |
| 26 break; |
| 27 if (node.isStyledElement() && !isMailHTMLBlockquoteElement(&node)) { |
| 28 wrappingStyle->mergeInlineAndImplicitStyleOfElement( |
| 29 toElement(&node), EditingStyle::DoNotOverrideValues, |
| 30 EditingStyle::EditingPropertiesInEffect); |
| 31 } |
| 32 } |
| 33 |
| 34 return wrappingStyle; |
| 35 } |
| 36 |
| 37 EditingStyle* |
| 38 EditingStyleUtilities::createWrappingStyleForAnnotatedSerialization( |
| 39 ContainerNode* context) { |
| 40 EditingStyle* wrappingStyle = |
| 41 EditingStyle::create(context, EditingStyle::EditingPropertiesInEffect); |
| 42 |
| 43 // Styles that Mail blockquotes contribute should only be placed on the Mail |
| 44 // blockquote, to help us differentiate those styles from ones that the user |
| 45 // has applied. This helps us get the color of content pasted into |
| 46 // blockquotes right. |
| 47 wrappingStyle->removeStyleAddedByElement(toHTMLElement(enclosingNodeOfType( |
| 48 firstPositionInOrBeforeNode(context), isMailHTMLBlockquoteElement, |
| 49 CanCrossEditingBoundary))); |
| 50 |
| 51 // Call collapseTextDecorationProperties first or otherwise it'll copy the |
| 52 // value over from in-effect to text-decorations. |
| 53 wrappingStyle->collapseTextDecorationProperties(); |
| 54 |
| 55 return wrappingStyle; |
| 56 } |
| 57 |
| 58 EditingStyle* EditingStyleUtilities::createStyleAtSelectionStart( |
| 59 const VisibleSelection& selection, |
| 60 bool shouldUseBackgroundColorInEffect, |
| 61 MutableStylePropertySet* styleToCheck) { |
| 62 if (selection.isNone()) |
| 63 return nullptr; |
| 64 |
| 65 Document& document = *selection.start().document(); |
| 66 |
| 67 DCHECK(!document.needsLayoutTreeUpdate()); |
| 68 DocumentLifecycle::DisallowTransitionScope disallowTransition( |
| 69 document.lifecycle()); |
| 70 |
| 71 Position position = adjustedSelectionStartForStyleComputation(selection); |
| 72 |
| 73 // If the pos is at the end of a text node, then this node is not fully |
| 74 // selected. Move it to the next deep equivalent position to avoid removing |
| 75 // the style from this node. |
| 76 // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we |
| 77 // want Position("world", 0) instead. |
| 78 // We only do this for range because caret at Position("hello", 5) in |
| 79 // <b>hello</b>world should give you font-weight: bold. |
| 80 Node* positionNode = position.computeContainerNode(); |
| 81 if (selection.isRange() && positionNode && positionNode->isTextNode() && |
| 82 position.computeOffsetInContainerNode() == |
| 83 positionNode->maxCharacterOffset()) |
| 84 position = nextVisuallyDistinctCandidate(position); |
| 85 |
| 86 Element* element = associatedElementOf(position); |
| 87 if (!element) |
| 88 return nullptr; |
| 89 |
| 90 EditingStyle* style = |
| 91 EditingStyle::create(element, EditingStyle::AllProperties); |
| 92 style->mergeTypingStyle(&element->document()); |
| 93 |
| 94 // If |element| has <sub> or <sup> ancestor element, apply the corresponding |
| 95 // style(vertical-align) to it so that document.queryCommandState() works with |
| 96 // the style. See bug http://crbug.com/582225. |
| 97 CSSValueID valueID = |
| 98 getIdentifierValue(styleToCheck, CSSPropertyVerticalAlign); |
| 99 if (valueID == CSSValueSub || valueID == CSSValueSuper) { |
| 100 CSSComputedStyleDeclaration* elementStyle = |
| 101 CSSComputedStyleDeclaration::create(element); |
| 102 // Find the ancestor that has CSSValueSub or CSSValueSuper as the value of |
| 103 // CSS vertical-align property. |
| 104 if (getIdentifierValue(elementStyle, CSSPropertyVerticalAlign) == |
| 105 CSSValueBaseline && |
| 106 hasAncestorVerticalAlignStyle(*element, valueID)) |
| 107 style->style()->setProperty(CSSPropertyVerticalAlign, valueID); |
| 108 } |
| 109 |
| 110 // If background color is transparent, traverse parent nodes until we hit a |
| 111 // different value or document root Also, if the selection is a range, ignore |
| 112 // the background color at the start of selection, and find the background |
| 113 // color of the common ancestor. |
| 114 if (shouldUseBackgroundColorInEffect && |
| 115 (selection.isRange() || hasTransparentBackgroundColor(style->style()))) { |
| 116 const EphemeralRange range(selection.toNormalizedEphemeralRange()); |
| 117 if (const CSSValue* value = |
| 118 backgroundColorValueInEffect(Range::commonAncestorContainer( |
| 119 range.startPosition().computeContainerNode(), |
| 120 range.endPosition().computeContainerNode()))) |
| 121 style->setProperty(CSSPropertyBackgroundColor, value->cssText()); |
| 122 } |
| 123 |
| 124 return style; |
| 125 } |
| 126 |
| 127 bool EditingStyleUtilities::hasAncestorVerticalAlignStyle(Node& node, |
| 128 CSSValueID value) { |
| 129 for (Node& runner : NodeTraversal::inclusiveAncestorsOf(node)) { |
| 130 CSSComputedStyleDeclaration* ancestorStyle = |
| 131 CSSComputedStyleDeclaration::create(&runner); |
| 132 if (getIdentifierValue(ancestorStyle, CSSPropertyVerticalAlign) == value) |
| 133 return true; |
| 134 } |
| 135 return false; |
| 136 } |
| 137 |
| 138 static bool isUnicodeBidiNestedOrMultipleEmbeddings(CSSValueID valueID) { |
| 139 return valueID == CSSValueEmbed || valueID == CSSValueBidiOverride || |
| 140 valueID == CSSValueWebkitIsolate || |
| 141 valueID == CSSValueWebkitIsolateOverride || |
| 142 valueID == CSSValueWebkitPlaintext || valueID == CSSValueIsolate || |
| 143 valueID == CSSValueIsolateOverride || valueID == CSSValuePlaintext; |
| 144 } |
| 145 |
| 146 WritingDirection EditingStyleUtilities::textDirectionForSelection( |
| 147 const VisibleSelection& selection, |
| 148 EditingStyle* typingStyle, |
| 149 bool& hasNestedOrMultipleEmbeddings) { |
| 150 hasNestedOrMultipleEmbeddings = true; |
| 151 |
| 152 if (selection.isNone()) |
| 153 return NaturalWritingDirection; |
| 154 |
| 155 Position position = mostForwardCaretPosition(selection.start()); |
| 156 |
| 157 Node* node = position.anchorNode(); |
| 158 if (!node) |
| 159 return NaturalWritingDirection; |
| 160 |
| 161 Position end; |
| 162 if (selection.isRange()) { |
| 163 end = mostBackwardCaretPosition(selection.end()); |
| 164 |
| 165 DCHECK(end.document()); |
| 166 const EphemeralRange caretRange(position.parentAnchoredEquivalent(), |
| 167 end.parentAnchoredEquivalent()); |
| 168 for (Node& n : caretRange.nodes()) { |
| 169 if (!n.isStyledElement()) |
| 170 continue; |
| 171 |
| 172 CSSComputedStyleDeclaration* style = |
| 173 CSSComputedStyleDeclaration::create(&n); |
| 174 const CSSValue* unicodeBidi = |
| 175 style->getPropertyCSSValue(CSSPropertyUnicodeBidi); |
| 176 if (!unicodeBidi || !unicodeBidi->isIdentifierValue()) |
| 177 continue; |
| 178 |
| 179 CSSValueID unicodeBidiValue = |
| 180 toCSSIdentifierValue(unicodeBidi)->getValueID(); |
| 181 if (isUnicodeBidiNestedOrMultipleEmbeddings(unicodeBidiValue)) |
| 182 return NaturalWritingDirection; |
| 183 } |
| 184 } |
| 185 |
| 186 if (selection.isCaret()) { |
| 187 WritingDirection direction; |
| 188 if (typingStyle && typingStyle->textDirection(direction)) { |
| 189 hasNestedOrMultipleEmbeddings = false; |
| 190 return direction; |
| 191 } |
| 192 node = selection.visibleStart().deepEquivalent().anchorNode(); |
| 193 } |
| 194 DCHECK(node); |
| 195 |
| 196 // The selection is either a caret with no typing attributes or a range in |
| 197 // which no embedding is added, so just use the start position to decide. |
| 198 Node* block = enclosingBlock(node); |
| 199 WritingDirection foundDirection = NaturalWritingDirection; |
| 200 |
| 201 for (Node& runner : NodeTraversal::inclusiveAncestorsOf(*node)) { |
| 202 if (runner == block) |
| 203 break; |
| 204 if (!runner.isStyledElement()) |
| 205 continue; |
| 206 |
| 207 Element* element = &toElement(runner); |
| 208 CSSComputedStyleDeclaration* style = |
| 209 CSSComputedStyleDeclaration::create(element); |
| 210 const CSSValue* unicodeBidi = |
| 211 style->getPropertyCSSValue(CSSPropertyUnicodeBidi); |
| 212 if (!unicodeBidi || !unicodeBidi->isIdentifierValue()) |
| 213 continue; |
| 214 |
| 215 CSSValueID unicodeBidiValue = |
| 216 toCSSIdentifierValue(unicodeBidi)->getValueID(); |
| 217 if (unicodeBidiValue == CSSValueNormal) |
| 218 continue; |
| 219 |
| 220 if (unicodeBidiValue == CSSValueBidiOverride) |
| 221 return NaturalWritingDirection; |
| 222 |
| 223 DCHECK(isEmbedOrIsolate(unicodeBidiValue)) << unicodeBidiValue; |
| 224 const CSSValue* direction = |
| 225 style->getPropertyCSSValue(CSSPropertyDirection); |
| 226 if (!direction || !direction->isIdentifierValue()) |
| 227 continue; |
| 228 |
| 229 int directionValue = toCSSIdentifierValue(direction)->getValueID(); |
| 230 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl) |
| 231 continue; |
| 232 |
| 233 if (foundDirection != NaturalWritingDirection) |
| 234 return NaturalWritingDirection; |
| 235 |
| 236 // In the range case, make sure that the embedding element persists until |
| 237 // the end of the range. |
| 238 if (selection.isRange() && !end.anchorNode()->isDescendantOf(element)) |
| 239 return NaturalWritingDirection; |
| 240 |
| 241 foundDirection = directionValue == CSSValueLtr |
| 242 ? LeftToRightWritingDirection |
| 243 : RightToLeftWritingDirection; |
| 244 } |
| 245 hasNestedOrMultipleEmbeddings = false; |
| 246 return foundDirection; |
| 247 } |
| 248 |
| 249 bool EditingStyleUtilities::isTransparentColorValue(const CSSValue* cssValue) { |
| 250 if (!cssValue) |
| 251 return true; |
| 252 if (cssValue->isColorValue()) |
| 253 return !toCSSColorValue(cssValue)->value().alpha(); |
| 254 if (!cssValue->isIdentifierValue()) |
| 255 return false; |
| 256 return toCSSIdentifierValue(cssValue)->getValueID() == CSSValueTransparent; |
| 257 } |
| 258 |
| 259 bool EditingStyleUtilities::hasTransparentBackgroundColor( |
| 260 CSSStyleDeclaration* style) { |
| 261 const CSSValue* cssValue = |
| 262 style->getPropertyCSSValueInternal(CSSPropertyBackgroundColor); |
| 263 return isTransparentColorValue(cssValue); |
| 264 } |
| 265 |
| 266 bool EditingStyleUtilities::hasTransparentBackgroundColor( |
| 267 StylePropertySet* style) { |
| 268 const CSSValue* cssValue = |
| 269 style->getPropertyCSSValue(CSSPropertyBackgroundColor); |
| 270 return isTransparentColorValue(cssValue); |
| 271 } |
| 272 |
| 273 const CSSValue* EditingStyleUtilities::backgroundColorValueInEffect( |
| 274 Node* node) { |
| 275 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) { |
| 276 CSSComputedStyleDeclaration* ancestorStyle = |
| 277 CSSComputedStyleDeclaration::create(ancestor); |
| 278 if (!hasTransparentBackgroundColor(ancestorStyle)) |
| 279 return ancestorStyle->getPropertyCSSValue(CSSPropertyBackgroundColor); |
| 280 } |
| 281 return nullptr; |
| 282 } |
| 283 |
| 284 } // namespace blink |
| OLD | NEW |