1/*
2 * Copyright (C) 2007, 2008, 2009, 2013 Apple Inc.
3 * Copyright (C) 2010, 2011 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "EditingStyle.h"
29
30#include "ApplyStyleCommand.h"
31#include "CSSComputedStyleDeclaration.h"
32#include "CSSFontStyleValue.h"
33#include "CSSParser.h"
34#include "CSSRuleList.h"
35#include "CSSStyleRule.h"
36#include "CSSValueList.h"
37#include "CSSValuePool.h"
38#include "Editing.h"
39#include "Editor.h"
40#include "Frame.h"
41#include "HTMLFontElement.h"
42#include "HTMLInterchange.h"
43#include "HTMLNames.h"
44#include "HTMLSpanElement.h"
45#include "Node.h"
46#include "NodeTraversal.h"
47#include "QualifiedName.h"
48#include "RenderElement.h"
49#include "RenderStyle.h"
50#include "StyleFontSizeFunctions.h"
51#include "StyleProperties.h"
52#include "StyleResolver.h"
53#include "StyleRule.h"
54#include "StyledElement.h"
55#include "VisibleUnits.h"
56#include <wtf/Optional.h>
57
58namespace WebCore {
59
60// Editing style properties must be preserved during editing operation.
61// e.g. when a user inserts a new paragraph, all properties listed here must be copied to the new paragraph.
62static const CSSPropertyID editingProperties[] = {
63 CSSPropertyCaretColor,
64 CSSPropertyColor,
65 CSSPropertyFontFamily,
66 CSSPropertyFontSize,
67 CSSPropertyFontStyle,
68 CSSPropertyFontVariantCaps,
69 CSSPropertyFontWeight,
70 CSSPropertyLetterSpacing,
71 CSSPropertyOrphans,
72 CSSPropertyTextAlign,
73 CSSPropertyTextIndent,
74 CSSPropertyTextTransform,
75 CSSPropertyWhiteSpace,
76 CSSPropertyWidows,
77 CSSPropertyWordSpacing,
78#if ENABLE(TOUCH_EVENTS)
79 CSSPropertyWebkitTapHighlightColor,
80#endif
81 CSSPropertyWebkitTextDecorationsInEffect,
82 CSSPropertyWebkitTextFillColor,
83#if ENABLE(TEXT_AUTOSIZING)
84 CSSPropertyWebkitTextSizeAdjust,
85#endif
86 CSSPropertyWebkitTextStrokeColor,
87 CSSPropertyWebkitTextStrokeWidth,
88
89 // Non-inheritable properties
90 CSSPropertyBackgroundColor,
91 CSSPropertyTextDecoration,
92};
93
94const unsigned numAllEditingProperties = WTF_ARRAY_LENGTH(editingProperties);
95const unsigned numInheritableEditingProperties = numAllEditingProperties - 2;
96
97enum EditingPropertiesToInclude { OnlyInheritableEditingProperties, AllEditingProperties };
98template <class StyleDeclarationType>
99static Ref<MutableStyleProperties> copyEditingProperties(StyleDeclarationType* style, EditingPropertiesToInclude type)
100{
101 if (type == AllEditingProperties)
102 return style->copyPropertiesInSet(editingProperties, numAllEditingProperties);
103 return style->copyPropertiesInSet(editingProperties, numInheritableEditingProperties);
104}
105
106static inline bool isEditingProperty(int id)
107{
108 for (auto& editingProperty : editingProperties) {
109 if (editingProperty == id)
110 return true;
111 }
112 return false;
113}
114
115static Ref<MutableStyleProperties> copyPropertiesFromComputedStyle(ComputedStyleExtractor& computedStyle, EditingStyle::PropertiesToInclude propertiesToInclude)
116{
117 switch (propertiesToInclude) {
118 case EditingStyle::OnlyEditingInheritableProperties:
119 return copyEditingProperties(&computedStyle, OnlyInheritableEditingProperties);
120 case EditingStyle::EditingPropertiesInEffect:
121 return copyEditingProperties(&computedStyle, AllEditingProperties);
122 case EditingStyle::AllProperties:
123 break;
124 }
125 return computedStyle.copyProperties();
126}
127
128static Ref<MutableStyleProperties> copyPropertiesFromComputedStyle(Node* node, EditingStyle::PropertiesToInclude propertiesToInclude)
129{
130 ComputedStyleExtractor computedStyle(node);
131 return copyPropertiesFromComputedStyle(computedStyle, propertiesToInclude);
132}
133
134static RefPtr<CSSValue> extractPropertyValue(const StyleProperties& style, CSSPropertyID propertyID)
135{
136 return style.getPropertyCSSValue(propertyID);
137}
138
139static RefPtr<CSSValue> extractPropertyValue(ComputedStyleExtractor& computedStyle, CSSPropertyID propertyID)
140{
141 return computedStyle.propertyValue(propertyID);
142}
143
144template<typename T>
145int identifierForStyleProperty(T& style, CSSPropertyID propertyID)
146{
147 RefPtr<CSSValue> value = extractPropertyValue(style, propertyID);
148 if (propertyID == CSSPropertyFontStyle && is<CSSFontStyleValue>(value) && downcast<CSSFontStyleValue>(value.get())->isItalicOrOblique())
149 return CSSValueItalic;
150 if (!is<CSSPrimitiveValue>(value))
151 return 0;
152 return downcast<CSSPrimitiveValue>(*value).valueID();
153}
154
155template<typename T> Ref<MutableStyleProperties> getPropertiesNotIn(StyleProperties& styleWithRedundantProperties, T& baseStyle);
156enum LegacyFontSizeMode { AlwaysUseLegacyFontSize, UseLegacyFontSizeOnlyIfPixelValuesMatch };
157static int legacyFontSizeFromCSSValue(Document&, CSSPrimitiveValue*, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode);
158static bool hasTransparentBackgroundColor(StyleProperties*);
159static RefPtr<CSSValue> backgroundColorInEffect(Node*);
160
161class HTMLElementEquivalent {
162 WTF_MAKE_FAST_ALLOCATED;
163public:
164 HTMLElementEquivalent(CSSPropertyID, CSSValueID primitiveValue, const QualifiedName& tagName);
165 virtual ~HTMLElementEquivalent() = default;
166
167 virtual bool matches(const Element& element) const { return !m_tagName || element.hasTagName(*m_tagName); }
168 virtual bool hasAttribute() const { return false; }
169 virtual bool propertyExistsInStyle(const EditingStyle& style) const { return style.m_mutableStyle && style.m_mutableStyle->getPropertyCSSValue(m_propertyID); }
170 virtual bool valueIsPresentInStyle(Element&, const EditingStyle&) const;
171 virtual void addToStyle(Element*, EditingStyle*) const;
172
173protected:
174 HTMLElementEquivalent(CSSPropertyID);
175 HTMLElementEquivalent(CSSPropertyID, const QualifiedName& tagName);
176 const CSSPropertyID m_propertyID;
177 const RefPtr<CSSPrimitiveValue> m_primitiveValue;
178 const QualifiedName* m_tagName { nullptr }; // We can store a pointer because HTML tag names are const global.
179};
180
181HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id)
182 : m_propertyID(id)
183{
184}
185
186HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, const QualifiedName& tagName)
187 : m_propertyID(id)
188 , m_tagName(&tagName)
189{
190}
191
192HTMLElementEquivalent::HTMLElementEquivalent(CSSPropertyID id, CSSValueID primitiveValue, const QualifiedName& tagName)
193 : m_propertyID(id)
194 , m_primitiveValue(CSSPrimitiveValue::createIdentifier(primitiveValue))
195 , m_tagName(&tagName)
196{
197 ASSERT(primitiveValue != CSSValueInvalid);
198}
199
200bool HTMLElementEquivalent::valueIsPresentInStyle(Element& element, const EditingStyle& style) const
201{
202 RefPtr<CSSValue> value = style.m_mutableStyle->getPropertyCSSValue(m_propertyID);
203 return matches(element) && is<CSSPrimitiveValue>(value) && downcast<CSSPrimitiveValue>(*value).valueID() == m_primitiveValue->valueID();
204}
205
206void HTMLElementEquivalent::addToStyle(Element*, EditingStyle* style) const
207{
208 style->setProperty(m_propertyID, m_primitiveValue->cssText());
209}
210
211class HTMLTextDecorationEquivalent : public HTMLElementEquivalent {
212public:
213 HTMLTextDecorationEquivalent(CSSValueID primitiveValue, const QualifiedName& tagName)
214 : HTMLElementEquivalent(CSSPropertyTextDecoration, primitiveValue, tagName)
215 , m_isUnderline(primitiveValue == CSSValueUnderline)
216 {
217 }
218
219 bool propertyExistsInStyle(const EditingStyle& style) const override
220 {
221 if (changeInStyle(style) != TextDecorationChange::None)
222 return true;
223
224 if (!style.m_mutableStyle)
225 return false;
226
227 auto& mutableStyle = *style.m_mutableStyle;
228 return mutableStyle.getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect)
229 || mutableStyle.getPropertyCSSValue(CSSPropertyTextDecoration);
230 }
231
232 bool valueIsPresentInStyle(Element& element, const EditingStyle& style) const override
233 {
234 if (!matches(element))
235 return false;
236 auto change = changeInStyle(style);
237 if (change != TextDecorationChange::None)
238 return change == TextDecorationChange::Add;
239 RefPtr<CSSValue> styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
240 if (!styleValue)
241 styleValue = style.m_mutableStyle->getPropertyCSSValue(CSSPropertyTextDecoration);
242 return is<CSSValueList>(styleValue) && downcast<CSSValueList>(*styleValue).hasValue(m_primitiveValue.get());
243 }
244
245private:
246 TextDecorationChange changeInStyle(const EditingStyle& style) const
247 {
248 return m_isUnderline ? style.underlineChange() : style.strikeThroughChange();
249 }
250
251 bool m_isUnderline;
252};
253
254class HTMLAttributeEquivalent : public HTMLElementEquivalent {
255public:
256 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& tagName, const QualifiedName& attrName);
257 HTMLAttributeEquivalent(CSSPropertyID, const QualifiedName& attrName);
258
259 bool matches(const Element& element) const override { return HTMLElementEquivalent::matches(element) && element.hasAttribute(m_attrName); }
260 bool hasAttribute() const override { return true; }
261 bool valueIsPresentInStyle(Element&, const EditingStyle&) const override;
262 void addToStyle(Element*, EditingStyle*) const override;
263 virtual RefPtr<CSSValue> attributeValueAsCSSValue(Element*) const;
264 inline const QualifiedName& attributeName() const { return m_attrName; }
265
266protected:
267 const QualifiedName& m_attrName; // We can store a reference because HTML attribute names are const global.
268};
269
270HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& tagName, const QualifiedName& attrName)
271 : HTMLElementEquivalent(id, tagName)
272 , m_attrName(attrName)
273{
274}
275
276HTMLAttributeEquivalent::HTMLAttributeEquivalent(CSSPropertyID id, const QualifiedName& attrName)
277 : HTMLElementEquivalent(id)
278 , m_attrName(attrName)
279{
280}
281
282bool HTMLAttributeEquivalent::valueIsPresentInStyle(Element& element, const EditingStyle& style) const
283{
284 RefPtr<CSSValue> value = attributeValueAsCSSValue(&element);
285 RefPtr<CSSValue> styleValue = style.m_mutableStyle->getPropertyCSSValue(m_propertyID);
286
287 return compareCSSValuePtr(value, styleValue);
288}
289
290void HTMLAttributeEquivalent::addToStyle(Element* element, EditingStyle* style) const
291{
292 if (RefPtr<CSSValue> value = attributeValueAsCSSValue(element))
293 style->setProperty(m_propertyID, value->cssText());
294}
295
296RefPtr<CSSValue> HTMLAttributeEquivalent::attributeValueAsCSSValue(Element* element) const
297{
298 ASSERT(element);
299 const AtomicString& value = element->getAttribute(m_attrName);
300 if (value.isNull())
301 return nullptr;
302
303 RefPtr<MutableStyleProperties> dummyStyle;
304 dummyStyle = MutableStyleProperties::create();
305 dummyStyle->setProperty(m_propertyID, value);
306 return dummyStyle->getPropertyCSSValue(m_propertyID);
307}
308
309class HTMLFontSizeEquivalent : public HTMLAttributeEquivalent {
310public:
311 HTMLFontSizeEquivalent();
312
313 RefPtr<CSSValue> attributeValueAsCSSValue(Element*) const override;
314};
315
316HTMLFontSizeEquivalent::HTMLFontSizeEquivalent()
317 : HTMLAttributeEquivalent(CSSPropertyFontSize, HTMLNames::fontTag, HTMLNames::sizeAttr)
318{
319}
320
321RefPtr<CSSValue> HTMLFontSizeEquivalent::attributeValueAsCSSValue(Element* element) const
322{
323 ASSERT(element);
324 const AtomicString& value = element->getAttribute(m_attrName);
325 if (value.isNull())
326 return nullptr;
327 CSSValueID size;
328 if (!HTMLFontElement::cssValueFromFontSizeNumber(value, size))
329 return nullptr;
330 return CSSPrimitiveValue::createIdentifier(size);
331}
332
333float EditingStyle::NoFontDelta = 0.0f;
334
335EditingStyle::EditingStyle()
336 : m_shouldUseFixedDefaultFontSize(false)
337 , m_underlineChange(static_cast<unsigned>(TextDecorationChange::None))
338 , m_strikeThroughChange(static_cast<unsigned>(TextDecorationChange::None))
339{
340}
341
342EditingStyle::EditingStyle(Node* node, PropertiesToInclude propertiesToInclude)
343 : EditingStyle()
344{
345 init(node, propertiesToInclude);
346}
347
348EditingStyle::EditingStyle(const Position& position, PropertiesToInclude propertiesToInclude)
349 : EditingStyle()
350{
351 init(position.deprecatedNode(), propertiesToInclude);
352}
353
354EditingStyle::EditingStyle(const CSSStyleDeclaration* style)
355 : EditingStyle()
356{
357 if (style)
358 m_mutableStyle = style->copyProperties();
359 extractFontSizeDelta();
360}
361
362EditingStyle::EditingStyle(const StyleProperties* style)
363 : EditingStyle()
364{
365 if (style)
366 m_mutableStyle = style->mutableCopy();
367 extractFontSizeDelta();
368}
369
370EditingStyle::EditingStyle(CSSPropertyID propertyID, const String& value)
371 : EditingStyle()
372{
373 setProperty(propertyID, value);
374 extractFontSizeDelta();
375}
376
377EditingStyle::EditingStyle(CSSPropertyID propertyID, CSSValueID value)
378 : EditingStyle()
379{
380 m_mutableStyle = MutableStyleProperties::create();
381 m_mutableStyle->setProperty(propertyID, value);
382 extractFontSizeDelta();
383}
384
385EditingStyle::~EditingStyle() = default;
386
387static Color cssValueToColor(CSSValue* colorValue)
388{
389 if (!is<CSSPrimitiveValue>(colorValue))
390 return Color::transparent;
391
392 CSSPrimitiveValue& primitiveColor = downcast<CSSPrimitiveValue>(*colorValue);
393 if (primitiveColor.isRGBColor())
394 return primitiveColor.color();
395
396 return CSSParser::parseColor(colorValue->cssText());
397}
398
399template<typename T>
400static inline Color textColorFromStyle(T& style)
401{
402 return cssValueToColor(extractPropertyValue(style, CSSPropertyColor).get());
403}
404
405template<typename T>
406static inline Color caretColorFromStyle(T& style)
407{
408 return cssValueToColor(extractPropertyValue(style, CSSPropertyCaretColor).get());
409}
410
411template<typename T>
412static inline Color backgroundColorFromStyle(T& style)
413{
414 return cssValueToColor(extractPropertyValue(style, CSSPropertyBackgroundColor).get());
415}
416
417static inline Color rgbaBackgroundColorInEffect(Node* node)
418{
419 return cssValueToColor(backgroundColorInEffect(node).get());
420}
421
422static int textAlignResolvingStartAndEnd(int textAlign, int direction)
423{
424 switch (textAlign) {
425 case CSSValueCenter:
426 case CSSValueWebkitCenter:
427 return CSSValueCenter;
428 case CSSValueJustify:
429 return CSSValueJustify;
430 case CSSValueLeft:
431 case CSSValueWebkitLeft:
432 return CSSValueLeft;
433 case CSSValueRight:
434 case CSSValueWebkitRight:
435 return CSSValueRight;
436 case CSSValueStart:
437 return direction != CSSValueRtl ? CSSValueLeft : CSSValueRight;
438 case CSSValueEnd:
439 return direction == CSSValueRtl ? CSSValueRight : CSSValueLeft;
440 }
441 return CSSValueInvalid;
442}
443
444template<typename T>
445static int textAlignResolvingStartAndEnd(T& style)
446{
447 return textAlignResolvingStartAndEnd(identifierForStyleProperty(style, CSSPropertyTextAlign), identifierForStyleProperty(style, CSSPropertyDirection));
448}
449
450void EditingStyle::init(Node* node, PropertiesToInclude propertiesToInclude)
451{
452 if (isTabSpanTextNode(node))
453 node = tabSpanNode(node)->parentNode();
454 else if (isTabSpanNode(node))
455 node = node->parentNode();
456
457 ComputedStyleExtractor computedStyleAtPosition(node);
458 // FIXME: It's strange to not set background-color and text-decoration when propertiesToInclude is EditingPropertiesInEffect.
459 // However editing/selection/contains-boundaries.html fails without this ternary.
460 m_mutableStyle = copyPropertiesFromComputedStyle(computedStyleAtPosition,
461 propertiesToInclude == EditingPropertiesInEffect ? OnlyEditingInheritableProperties : propertiesToInclude);
462
463 if (propertiesToInclude == EditingPropertiesInEffect) {
464 if (RefPtr<CSSValue> value = backgroundColorInEffect(node))
465 m_mutableStyle->setProperty(CSSPropertyBackgroundColor, value->cssText());
466 if (RefPtr<CSSValue> value = computedStyleAtPosition.propertyValue(CSSPropertyWebkitTextDecorationsInEffect)) {
467 m_mutableStyle->setProperty(CSSPropertyTextDecoration, value->cssText());
468 m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
469 }
470 }
471
472 if (node && node->computedStyle()) {
473 auto* renderStyle = node->computedStyle();
474 removeTextFillAndStrokeColorsIfNeeded(renderStyle);
475 if (renderStyle->fontDescription().keywordSize())
476 m_mutableStyle->setProperty(CSSPropertyFontSize, computedStyleAtPosition.getFontSizeCSSValuePreferringKeyword()->cssText());
477 }
478
479 m_shouldUseFixedDefaultFontSize = computedStyleAtPosition.useFixedFontDefaultSize();
480 extractFontSizeDelta();
481}
482
483void EditingStyle::removeTextFillAndStrokeColorsIfNeeded(const RenderStyle* renderStyle)
484{
485 // If a node's text fill color is invalid, then its children use
486 // their font-color as their text fill color (they don't
487 // inherit it). Likewise for stroke color.
488 if (!renderStyle->textFillColor().isValid())
489 m_mutableStyle->removeProperty(CSSPropertyWebkitTextFillColor);
490 if (!renderStyle->textStrokeColor().isValid())
491 m_mutableStyle->removeProperty(CSSPropertyWebkitTextStrokeColor);
492}
493
494void EditingStyle::setProperty(CSSPropertyID propertyID, const String& value, bool important)
495{
496 if (!m_mutableStyle)
497 m_mutableStyle = MutableStyleProperties::create();
498
499 m_mutableStyle->setProperty(propertyID, value, important);
500}
501
502void EditingStyle::extractFontSizeDelta()
503{
504 if (!m_mutableStyle)
505 return;
506
507 if (m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize)) {
508 // Explicit font size overrides any delta.
509 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
510 return;
511 }
512
513 // Get the adjustment amount out of the style.
514 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitFontSizeDelta);
515 if (!is<CSSPrimitiveValue>(value))
516 return;
517
518 CSSPrimitiveValue& primitiveValue = downcast<CSSPrimitiveValue>(*value);
519
520 // Only PX handled now. If we handle more types in the future, perhaps
521 // a switch statement here would be more appropriate.
522 if (!primitiveValue.isPx())
523 return;
524
525 m_fontSizeDelta = primitiveValue.floatValue();
526 m_mutableStyle->removeProperty(CSSPropertyWebkitFontSizeDelta);
527}
528
529bool EditingStyle::isEmpty() const
530{
531 return (!m_mutableStyle || m_mutableStyle->isEmpty()) && m_fontSizeDelta == NoFontDelta
532 && underlineChange() == TextDecorationChange::None && strikeThroughChange() == TextDecorationChange::None;
533}
534
535Ref<MutableStyleProperties> EditingStyle::styleWithResolvedTextDecorations() const
536{
537 bool hasTextDecorationChanges = underlineChange() != TextDecorationChange::None || strikeThroughChange() != TextDecorationChange::None;
538 if (m_mutableStyle && !hasTextDecorationChanges)
539 return *m_mutableStyle;
540
541 Ref<MutableStyleProperties> style = m_mutableStyle ? m_mutableStyle->mutableCopy() : MutableStyleProperties::create();
542
543 Ref<CSSValueList> valueList = CSSValueList::createSpaceSeparated();
544 if (underlineChange() == TextDecorationChange::Add)
545 valueList->append(CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline));
546 if (strikeThroughChange() == TextDecorationChange::Add)
547 valueList->append(CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough));
548
549 if (valueList->length())
550 style->setProperty(CSSPropertyTextDecoration, valueList.ptr());
551 else
552 style->setProperty(CSSPropertyTextDecoration, CSSValuePool::singleton().createIdentifierValue(CSSValueNone));
553
554 return style;
555}
556
557Optional<WritingDirection> EditingStyle::textDirection() const
558{
559 if (!m_mutableStyle)
560 return WTF::nullopt;
561
562 RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
563 if (!is<CSSPrimitiveValue>(unicodeBidi))
564 return WTF::nullopt;
565
566 CSSValueID unicodeBidiValue = downcast<CSSPrimitiveValue>(*unicodeBidi).valueID();
567 if (unicodeBidiValue == CSSValueEmbed) {
568 RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
569 if (!is<CSSPrimitiveValue>(direction))
570 return WTF::nullopt;
571
572 return downcast<CSSPrimitiveValue>(*direction).valueID() == CSSValueLtr ? WritingDirection::LeftToRight : WritingDirection::RightToLeft;
573 }
574
575 if (unicodeBidiValue == CSSValueNormal)
576 return WritingDirection::Natural;
577
578 return WTF::nullopt;
579}
580
581void EditingStyle::setStyle(RefPtr<MutableStyleProperties>&& style)
582{
583 m_mutableStyle = WTFMove(style);
584 // FIXME: We should be able to figure out whether or not font is fixed width for mutable style.
585 // We need to check font-family is monospace as in FontDescription but we don't want to duplicate code here.
586 m_shouldUseFixedDefaultFontSize = false;
587 extractFontSizeDelta();
588}
589
590void EditingStyle::overrideWithStyle(const StyleProperties& style)
591{
592 return mergeStyle(&style, OverrideValues);
593}
594
595static void applyTextDecorationChangeToValueList(CSSValueList& valueList, TextDecorationChange change, Ref<CSSPrimitiveValue>&& value)
596{
597 switch (change) {
598 case TextDecorationChange::None:
599 break;
600 case TextDecorationChange::Add:
601 valueList.append(WTFMove(value));
602 break;
603 case TextDecorationChange::Remove:
604 valueList.removeAll(&value.get());
605 break;
606 }
607}
608
609void EditingStyle::overrideTypingStyleAt(const EditingStyle& style, const Position& position)
610{
611 mergeStyle(style.m_mutableStyle.get(), OverrideValues);
612
613 m_fontSizeDelta += style.m_fontSizeDelta;
614
615 prepareToApplyAt(position, EditingStyle::PreserveWritingDirection);
616
617 auto underlineChange = style.underlineChange();
618 auto strikeThroughChange = style.strikeThroughChange();
619 if (underlineChange == TextDecorationChange::None && strikeThroughChange == TextDecorationChange::None)
620 return;
621
622 if (!m_mutableStyle)
623 m_mutableStyle = MutableStyleProperties::create();
624
625 auto& cssValuePool = CSSValuePool::singleton();
626 Ref<CSSPrimitiveValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline);
627 Ref<CSSPrimitiveValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough);
628 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
629 RefPtr<CSSValueList> valueList;
630 if (value && value->isValueList()) {
631 valueList = downcast<CSSValueList>(*value).copy();
632 applyTextDecorationChangeToValueList(*valueList, underlineChange, WTFMove(underline));
633 applyTextDecorationChangeToValueList(*valueList, strikeThroughChange, WTFMove(lineThrough));
634 } else {
635 valueList = CSSValueList::createSpaceSeparated();
636 if (underlineChange == TextDecorationChange::Add)
637 valueList->append(WTFMove(underline));
638 if (strikeThroughChange == TextDecorationChange::Add)
639 valueList->append(WTFMove(lineThrough));
640 }
641 m_mutableStyle->setProperty(CSSPropertyWebkitTextDecorationsInEffect, valueList.get());
642}
643
644void EditingStyle::clear()
645{
646 m_mutableStyle = nullptr;
647 m_shouldUseFixedDefaultFontSize = false;
648 m_fontSizeDelta = NoFontDelta;
649 setUnderlineChange(TextDecorationChange::None);
650 setStrikeThroughChange(TextDecorationChange::None);
651}
652
653Ref<EditingStyle> EditingStyle::copy() const
654{
655 auto copy = EditingStyle::create();
656 if (m_mutableStyle)
657 copy->m_mutableStyle = m_mutableStyle->mutableCopy();
658 copy->m_shouldUseFixedDefaultFontSize = m_shouldUseFixedDefaultFontSize;
659 copy->m_underlineChange = m_underlineChange;
660 copy->m_strikeThroughChange = m_strikeThroughChange;
661 copy->m_fontSizeDelta = m_fontSizeDelta;
662 return copy;
663}
664
665Ref<EditingStyle> EditingStyle::extractAndRemoveBlockProperties()
666{
667 auto blockProperties = EditingStyle::create();
668 if (!m_mutableStyle)
669 return blockProperties;
670
671 blockProperties->m_mutableStyle = m_mutableStyle->copyBlockProperties();
672 m_mutableStyle->removeBlockProperties();
673
674 return blockProperties;
675}
676
677Ref<EditingStyle> EditingStyle::extractAndRemoveTextDirection()
678{
679 auto textDirection = EditingStyle::create();
680 textDirection->m_mutableStyle = MutableStyleProperties::create();
681 textDirection->m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, CSSValueEmbed, m_mutableStyle->propertyIsImportant(CSSPropertyUnicodeBidi));
682 textDirection->m_mutableStyle->setProperty(CSSPropertyDirection, m_mutableStyle->getPropertyValue(CSSPropertyDirection),
683 m_mutableStyle->propertyIsImportant(CSSPropertyDirection));
684
685 m_mutableStyle->removeProperty(CSSPropertyUnicodeBidi);
686 m_mutableStyle->removeProperty(CSSPropertyDirection);
687
688 return textDirection;
689}
690
691void EditingStyle::removeBlockProperties()
692{
693 if (!m_mutableStyle)
694 return;
695
696 m_mutableStyle->removeBlockProperties();
697}
698
699void EditingStyle::removeStyleAddedByNode(Node* node)
700{
701 if (!node || !node->parentNode())
702 return;
703 RefPtr<MutableStyleProperties> parentStyle = copyPropertiesFromComputedStyle(node->parentNode(), EditingPropertiesInEffect);
704 RefPtr<MutableStyleProperties> nodeStyle = copyPropertiesFromComputedStyle(node, EditingPropertiesInEffect);
705 removeEquivalentProperties(*parentStyle);
706 removeEquivalentProperties(*nodeStyle);
707}
708
709void EditingStyle::removeStyleConflictingWithStyleOfNode(Node& node)
710{
711 if (!node.parentNode() || !m_mutableStyle)
712 return;
713
714 RefPtr<MutableStyleProperties> parentStyle = copyPropertiesFromComputedStyle(node.parentNode(), EditingPropertiesInEffect);
715 auto nodeStyle = EditingStyle::create(&node, EditingPropertiesInEffect);
716 nodeStyle->removeEquivalentProperties(*parentStyle);
717
718 MutableStyleProperties* style = nodeStyle->style();
719 unsigned propertyCount = style->propertyCount();
720 for (unsigned i = 0; i < propertyCount; ++i)
721 m_mutableStyle->removeProperty(style->propertyAt(i).id());
722}
723
724void EditingStyle::collapseTextDecorationProperties()
725{
726 if (!m_mutableStyle)
727 return;
728
729 RefPtr<CSSValue> textDecorationsInEffect = m_mutableStyle->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
730 if (!textDecorationsInEffect)
731 return;
732
733 if (textDecorationsInEffect->isValueList())
734 m_mutableStyle->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText(), m_mutableStyle->propertyIsImportant(CSSPropertyTextDecoration));
735 else
736 m_mutableStyle->removeProperty(CSSPropertyTextDecoration);
737 m_mutableStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
738}
739
740// CSS properties that create a visual difference only when applied to text.
741static const CSSPropertyID textOnlyProperties[] = {
742 CSSPropertyTextDecoration,
743 CSSPropertyWebkitTextDecorationsInEffect,
744 CSSPropertyFontStyle,
745 CSSPropertyFontWeight,
746 CSSPropertyColor,
747};
748
749TriState EditingStyle::triStateOfStyle(EditingStyle* style) const
750{
751 if (!style || !style->m_mutableStyle)
752 return FalseTriState;
753 return triStateOfStyle(*style->m_mutableStyle, DoNotIgnoreTextOnlyProperties);
754}
755
756template<typename T>
757TriState EditingStyle::triStateOfStyle(T& styleToCompare, ShouldIgnoreTextOnlyProperties shouldIgnoreTextOnlyProperties) const
758{
759 if (!m_mutableStyle)
760 return TrueTriState;
761
762 RefPtr<MutableStyleProperties> difference = getPropertiesNotIn(*m_mutableStyle, styleToCompare);
763
764 if (shouldIgnoreTextOnlyProperties == IgnoreTextOnlyProperties)
765 difference->removePropertiesInSet(textOnlyProperties, WTF_ARRAY_LENGTH(textOnlyProperties));
766
767 if (difference->isEmpty())
768 return TrueTriState;
769 if (difference->propertyCount() == m_mutableStyle->propertyCount())
770 return FalseTriState;
771
772 return MixedTriState;
773}
774
775TriState EditingStyle::triStateOfStyle(const VisibleSelection& selection) const
776{
777 if (!selection.isCaretOrRange())
778 return FalseTriState;
779
780 if (selection.isCaret())
781 return triStateOfStyle(EditingStyle::styleAtSelectionStart(selection).get());
782
783 TriState state = FalseTriState;
784 bool nodeIsStart = true;
785 for (Node* node = selection.start().deprecatedNode(); node; node = NodeTraversal::next(*node)) {
786 if (node->renderer() && node->hasEditableStyle()) {
787 ComputedStyleExtractor computedStyle(node);
788 TriState nodeState = triStateOfStyle(computedStyle, node->isTextNode() ? EditingStyle::DoNotIgnoreTextOnlyProperties : EditingStyle::IgnoreTextOnlyProperties);
789 if (nodeIsStart) {
790 state = nodeState;
791 nodeIsStart = false;
792 } else if (state != nodeState && node->isTextNode()) {
793 state = MixedTriState;
794 break;
795 }
796 }
797
798 if (node == selection.end().deprecatedNode())
799 break;
800 }
801
802 return state;
803}
804
805static RefPtr<CSSValueList> textDecorationValueList(const StyleProperties& properties)
806{
807 RefPtr<CSSValue> value = properties.getPropertyCSSValue(CSSPropertyTextDecoration);
808 if (!is<CSSValueList>(value))
809 return nullptr;
810 return downcast<CSSValueList>(value.get());
811}
812
813bool EditingStyle::conflictsWithInlineStyleOfElement(StyledElement& element, RefPtr<MutableStyleProperties>* newInlineStylePtr, EditingStyle* extractedStyle) const
814{
815 const StyleProperties* inlineStyle = element.inlineStyle();
816 if (!inlineStyle)
817 return false;
818 bool conflicts = false;
819 RefPtr<MutableStyleProperties> newInlineStyle;
820 if (newInlineStylePtr) {
821 newInlineStyle = inlineStyle->mutableCopy();
822 *newInlineStylePtr = newInlineStyle;
823 }
824
825 bool shouldRemoveUnderline = underlineChange() == TextDecorationChange::Remove;
826 bool shouldRemoveStrikeThrough = strikeThroughChange() == TextDecorationChange::Remove;
827 if (shouldRemoveUnderline || shouldRemoveStrikeThrough) {
828 if (RefPtr<CSSValueList> valueList = textDecorationValueList(*inlineStyle)) {
829 auto newValueList = valueList->copy();
830 auto extractedValueList = CSSValueList::createSpaceSeparated();
831
832 Ref<CSSPrimitiveValue> underline = CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline);
833 if (shouldRemoveUnderline && valueList->hasValue(underline.ptr())) {
834 if (!newInlineStyle)
835 return true;
836 newValueList->removeAll(underline.ptr());
837 extractedValueList->append(WTFMove(underline));
838 }
839
840 Ref<CSSPrimitiveValue> lineThrough = CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough);
841 if (shouldRemoveStrikeThrough && valueList->hasValue(lineThrough.ptr())) {
842 if (!newInlineStyle)
843 return true;
844 newValueList->removeAll(lineThrough.ptr());
845 extractedValueList->append(WTFMove(lineThrough));
846 }
847
848 if (extractedValueList->length()) {
849 conflicts = true;
850 if (newValueList->length())
851 newInlineStyle->setProperty(CSSPropertyTextDecoration, WTFMove(newValueList));
852 else
853 newInlineStyle->removeProperty(CSSPropertyTextDecoration);
854
855 if (extractedStyle) {
856 bool isImportant = inlineStyle->propertyIsImportant(CSSPropertyTextDecoration);
857 extractedStyle->setProperty(CSSPropertyTextDecoration, extractedValueList->cssText(), isImportant);
858 }
859 }
860 }
861 }
862
863 unsigned propertyCount = m_mutableStyle ? m_mutableStyle->propertyCount() : 0;
864 for (unsigned i = 0; i < propertyCount; ++i) {
865 CSSPropertyID propertyID = m_mutableStyle->propertyAt(i).id();
866
867 // We don't override whitespace property of a tab span because that would collapse the tab into a space.
868 if (propertyID == CSSPropertyWhiteSpace && isTabSpanNode(&element))
869 continue;
870
871 if (propertyID == CSSPropertyWebkitTextDecorationsInEffect && inlineStyle->getPropertyCSSValue(CSSPropertyTextDecoration)) {
872 if (!newInlineStyle)
873 return true;
874 conflicts = true;
875 newInlineStyle->removeProperty(CSSPropertyTextDecoration);
876 if (extractedStyle)
877 extractedStyle->setProperty(CSSPropertyTextDecoration, inlineStyle->getPropertyValue(CSSPropertyTextDecoration), inlineStyle->propertyIsImportant(CSSPropertyTextDecoration));
878 }
879
880 if (!inlineStyle->getPropertyCSSValue(propertyID))
881 continue;
882
883 if (propertyID == CSSPropertyUnicodeBidi && inlineStyle->getPropertyCSSValue(CSSPropertyDirection)) {
884 if (!newInlineStyle)
885 return true;
886 conflicts = true;
887 newInlineStyle->removeProperty(CSSPropertyDirection);
888 if (extractedStyle)
889 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
890 }
891
892 if (!newInlineStyle)
893 return true;
894
895 conflicts = true;
896 newInlineStyle->removeProperty(propertyID);
897 if (extractedStyle)
898 extractedStyle->setProperty(propertyID, inlineStyle->getPropertyValue(propertyID), inlineStyle->propertyIsImportant(propertyID));
899 }
900
901 return conflicts;
902}
903
904static const Vector<const HTMLElementEquivalent*>& htmlElementEquivalents()
905{
906 static const auto equivalents = makeNeverDestroyed(Vector<const HTMLElementEquivalent*> {
907 new HTMLElementEquivalent(CSSPropertyFontWeight, CSSValueBold, HTMLNames::bTag),
908 new HTMLElementEquivalent(CSSPropertyFontWeight, CSSValueBold, HTMLNames::strongTag),
909 new HTMLElementEquivalent(CSSPropertyVerticalAlign, CSSValueSub, HTMLNames::subTag),
910 new HTMLElementEquivalent(CSSPropertyVerticalAlign, CSSValueSuper, HTMLNames::supTag),
911 new HTMLElementEquivalent(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::iTag),
912 new HTMLElementEquivalent(CSSPropertyFontStyle, CSSValueItalic, HTMLNames::emTag),
913
914 new HTMLTextDecorationEquivalent(CSSValueUnderline, HTMLNames::uTag),
915 new HTMLTextDecorationEquivalent(CSSValueLineThrough, HTMLNames::sTag),
916 new HTMLTextDecorationEquivalent(CSSValueLineThrough, HTMLNames::strikeTag),
917 });
918 return equivalents;
919}
920
921
922bool EditingStyle::conflictsWithImplicitStyleOfElement(HTMLElement& element, EditingStyle* extractedStyle, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
923{
924 if (isEmpty())
925 return false;
926
927 for (auto& equivalent : htmlElementEquivalents()) {
928 if (equivalent->matches(element) && equivalent->propertyExistsInStyle(*this)
929 && (shouldExtractMatchingStyle == ExtractMatchingStyle || !equivalent->valueIsPresentInStyle(element, *this))) {
930 if (extractedStyle)
931 equivalent->addToStyle(&element, extractedStyle);
932 return true;
933 }
934 }
935 return false;
936}
937
938static const Vector<const HTMLAttributeEquivalent*>& htmlAttributeEquivalents()
939{
940 static const auto equivalents = makeNeverDestroyed(Vector<const HTMLAttributeEquivalent*> {
941 // elementIsStyledSpanOrHTMLEquivalent depends on the fact each HTMLAttriuteEquivalent matches exactly one attribute
942 // of exactly one element except dirAttr.
943 new HTMLAttributeEquivalent(CSSPropertyColor, HTMLNames::fontTag, HTMLNames::colorAttr),
944 new HTMLAttributeEquivalent(CSSPropertyFontFamily, HTMLNames::fontTag, HTMLNames::faceAttr),
945 new HTMLFontSizeEquivalent,
946
947 new HTMLAttributeEquivalent(CSSPropertyDirection, HTMLNames::dirAttr),
948 new HTMLAttributeEquivalent(CSSPropertyUnicodeBidi, HTMLNames::dirAttr),
949 });
950 return equivalents;
951}
952
953bool EditingStyle::conflictsWithImplicitStyleOfAttributes(HTMLElement& element) const
954{
955 if (isEmpty())
956 return false;
957
958 for (auto& equivalent : htmlAttributeEquivalents()) {
959 if (equivalent->matches(element) && equivalent->propertyExistsInStyle(*this) && !equivalent->valueIsPresentInStyle(element, *this))
960 return true;
961 }
962
963 return false;
964}
965
966bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement& element, ShouldPreserveWritingDirection shouldPreserveWritingDirection,
967 EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle shouldExtractMatchingStyle) const
968{
969 // HTMLAttributeEquivalent::addToStyle doesn't support unicode-bidi and direction properties
970 ASSERT(!extractedStyle || shouldPreserveWritingDirection == PreserveWritingDirection);
971 if (!m_mutableStyle)
972 return false;
973
974 bool removed = false;
975 for (auto& equivalent : htmlAttributeEquivalents()) {
976 // unicode-bidi and direction are pushed down separately so don't push down with other styles.
977 if (shouldPreserveWritingDirection == PreserveWritingDirection && equivalent->attributeName() == HTMLNames::dirAttr)
978 continue;
979
980 if (!equivalent->matches(element) || !equivalent->propertyExistsInStyle(*this)
981 || (shouldExtractMatchingStyle == DoNotExtractMatchingStyle && equivalent->valueIsPresentInStyle(element, *this)))
982 continue;
983
984 if (extractedStyle)
985 equivalent->addToStyle(&element, extractedStyle);
986 conflictingAttributes.append(equivalent->attributeName());
987 removed = true;
988 }
989
990 return removed;
991}
992
993bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node& node) const
994{
995 if (isEmpty())
996 return true;
997 ComputedStyleExtractor computedStyle(&node);
998
999 bool shouldAddUnderline = underlineChange() == TextDecorationChange::Add;
1000 bool shouldAddLineThrough = strikeThroughChange() == TextDecorationChange::Add;
1001 if (shouldAddUnderline || shouldAddLineThrough) {
1002 bool hasUnderline = false;
1003 bool hasLineThrough = false;
1004 if (RefPtr<CSSValue> value = computedStyle.propertyValue(CSSPropertyTextDecoration)) {
1005 if (value->isValueList()) {
1006 auto& cssValuePool = CSSValuePool::singleton();
1007 const CSSValueList& valueList = downcast<CSSValueList>(*value);
1008 hasUnderline = valueList.hasValue(cssValuePool.createIdentifierValue(CSSValueUnderline).ptr());
1009 hasLineThrough = valueList.hasValue(cssValuePool.createIdentifierValue(CSSValueLineThrough).ptr());
1010 }
1011 }
1012 if ((shouldAddUnderline && !hasUnderline) || (shouldAddLineThrough && !hasLineThrough))
1013 return false;
1014 }
1015
1016 return !m_mutableStyle || getPropertiesNotIn(*m_mutableStyle, computedStyle)->isEmpty();
1017}
1018
1019bool EditingStyle::elementIsStyledSpanOrHTMLEquivalent(const HTMLElement& element)
1020{
1021 bool elementIsSpanOrElementEquivalent = false;
1022 if (element.hasTagName(HTMLNames::spanTag))
1023 elementIsSpanOrElementEquivalent = true;
1024 else {
1025 for (auto& equivalent : htmlElementEquivalents()) {
1026 if (equivalent->matches(element)) {
1027 elementIsSpanOrElementEquivalent = true;
1028 break;
1029 }
1030 }
1031 }
1032
1033 if (!element.hasAttributes())
1034 return elementIsSpanOrElementEquivalent; // span, b, etc... without any attributes
1035
1036 unsigned matchedAttributes = 0;
1037 for (auto& equivalent : htmlAttributeEquivalents()) {
1038 if (equivalent->matches(element) && equivalent->attributeName() != HTMLNames::dirAttr)
1039 matchedAttributes++;
1040 }
1041
1042 if (!elementIsSpanOrElementEquivalent && !matchedAttributes)
1043 return false; // element is not a span, a html element equivalent, or font element.
1044
1045 if (element.attributeWithoutSynchronization(HTMLNames::classAttr) == AppleStyleSpanClass)
1046 matchedAttributes++;
1047
1048 if (element.hasAttribute(HTMLNames::styleAttr)) {
1049 if (const StyleProperties* style = element.inlineStyle()) {
1050 unsigned propertyCount = style->propertyCount();
1051 for (unsigned i = 0; i < propertyCount; ++i) {
1052 if (!isEditingProperty(style->propertyAt(i).id()))
1053 return false;
1054 }
1055 }
1056 matchedAttributes++;
1057 }
1058
1059 // font with color attribute, span with style attribute, etc...
1060 ASSERT(matchedAttributes <= element.attributeCount());
1061 return matchedAttributes >= element.attributeCount();
1062}
1063
1064void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection)
1065{
1066 if (!m_mutableStyle)
1067 return;
1068
1069 // ReplaceSelectionCommand::handleStyleSpans() requires that this function only removes the editing style.
1070 // If this function was modified in the future to delete all redundant properties, then add a boolean value to indicate
1071 // which one of editingStyleAtPosition or computedStyle is called.
1072 auto editingStyleAtPosition = EditingStyle::create(position, EditingPropertiesInEffect);
1073 StyleProperties* styleAtPosition = editingStyleAtPosition->m_mutableStyle.get();
1074
1075 RefPtr<CSSValue> unicodeBidi;
1076 RefPtr<CSSValue> direction;
1077 if (shouldPreserveWritingDirection == PreserveWritingDirection) {
1078 unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi);
1079 direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection);
1080 }
1081
1082 removeEquivalentProperties(*styleAtPosition);
1083
1084 if (textAlignResolvingStartAndEnd(*m_mutableStyle) == textAlignResolvingStartAndEnd(*styleAtPosition))
1085 m_mutableStyle->removeProperty(CSSPropertyTextAlign);
1086
1087 if (equalIgnoringSemanticColor(textColorFromStyle(*m_mutableStyle), textColorFromStyle(*styleAtPosition)))
1088 m_mutableStyle->removeProperty(CSSPropertyColor);
1089
1090 if (equalIgnoringSemanticColor(caretColorFromStyle(*m_mutableStyle), caretColorFromStyle(*styleAtPosition)))
1091 m_mutableStyle->removeProperty(CSSPropertyCaretColor);
1092
1093 if (hasTransparentBackgroundColor(m_mutableStyle.get())
1094 || cssValueToColor(m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor).get()) == rgbaBackgroundColorInEffect(position.containerNode()))
1095 m_mutableStyle->removeProperty(CSSPropertyBackgroundColor);
1096
1097 if (is<CSSPrimitiveValue>(unicodeBidi)) {
1098 m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSValueID>(downcast<CSSPrimitiveValue>(*unicodeBidi).valueID()));
1099 if (is<CSSPrimitiveValue>(direction))
1100 m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSValueID>(downcast<CSSPrimitiveValue>(*direction).valueID()));
1101 }
1102}
1103
1104void EditingStyle::mergeTypingStyle(Document& document)
1105{
1106 RefPtr<EditingStyle> typingStyle = document.frame()->selection().typingStyle();
1107 if (!typingStyle || typingStyle == this)
1108 return;
1109
1110 mergeStyle(typingStyle->style(), OverrideValues);
1111}
1112
1113void EditingStyle::mergeInlineStyleOfElement(StyledElement& element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
1114{
1115 if (!element.inlineStyle())
1116 return;
1117
1118 switch (propertiesToInclude) {
1119 case AllProperties:
1120 mergeStyle(element.inlineStyle(), mode);
1121 return;
1122 case OnlyEditingInheritableProperties:
1123 mergeStyle(copyEditingProperties(element.inlineStyle(), OnlyInheritableEditingProperties).ptr(), mode);
1124 return;
1125 case EditingPropertiesInEffect:
1126 mergeStyle(copyEditingProperties(element.inlineStyle(), AllEditingProperties).ptr(), mode);
1127 return;
1128 }
1129}
1130
1131static inline bool elementMatchesAndPropertyIsNotInInlineStyleDecl(const HTMLElementEquivalent& equivalent, const StyledElement& element,
1132 EditingStyle::CSSPropertyOverrideMode mode, EditingStyle& style)
1133{
1134 if (!equivalent.matches(element))
1135 return false;
1136 if (mode != EditingStyle::OverrideValues && equivalent.propertyExistsInStyle(style))
1137 return false;
1138
1139 return !element.inlineStyle() || !equivalent.propertyExistsInStyle(EditingStyle::create(element.inlineStyle()).get());
1140}
1141
1142static RefPtr<MutableStyleProperties> extractEditingProperties(const StyleProperties* style, EditingStyle::PropertiesToInclude propertiesToInclude)
1143{
1144 if (!style)
1145 return nullptr;
1146
1147 switch (propertiesToInclude) {
1148 case EditingStyle::OnlyEditingInheritableProperties:
1149 return copyEditingProperties(style, OnlyInheritableEditingProperties);
1150 case EditingStyle::AllProperties:
1151 case EditingStyle::EditingPropertiesInEffect:
1152 break;
1153 }
1154 return copyEditingProperties(style, AllEditingProperties);
1155}
1156
1157void EditingStyle::mergeInlineAndImplicitStyleOfElement(StyledElement& element, CSSPropertyOverrideMode mode, PropertiesToInclude propertiesToInclude)
1158{
1159 auto styleFromRules = EditingStyle::create();
1160 styleFromRules->mergeStyleFromRulesForSerialization(element);
1161
1162 if (element.inlineStyle())
1163 styleFromRules->m_mutableStyle->mergeAndOverrideOnConflict(*element.inlineStyle());
1164
1165 styleFromRules->m_mutableStyle = extractEditingProperties(styleFromRules->m_mutableStyle.get(), propertiesToInclude);
1166 mergeStyle(styleFromRules->m_mutableStyle.get(), mode);
1167
1168 for (auto& equivalent : htmlElementEquivalents()) {
1169 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(*equivalent, element, mode, *this))
1170 equivalent->addToStyle(&element, this);
1171 }
1172
1173 for (auto& equivalent : htmlAttributeEquivalents()) {
1174 if (equivalent->attributeName() == HTMLNames::dirAttr)
1175 continue; // We don't want to include directionality
1176 if (elementMatchesAndPropertyIsNotInInlineStyleDecl(*equivalent, element, mode, *this))
1177 equivalent->addToStyle(&element, this);
1178 }
1179}
1180
1181Ref<EditingStyle> EditingStyle::wrappingStyleForSerialization(Node& context, bool shouldAnnotate)
1182{
1183 if (shouldAnnotate) {
1184 auto wrappingStyle = EditingStyle::create(&context, EditingStyle::EditingPropertiesInEffect);
1185
1186 // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote,
1187 // to help us differentiate those styles from ones that the user has applied.
1188 // This helps us get the color of content pasted into blockquotes right.
1189 wrappingStyle->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInOrBeforeNode(&context), isMailBlockquote, CanCrossEditingBoundary));
1190
1191 // Call collapseTextDecorationProperties first or otherwise it'll copy the value over from in-effect to text-decorations.
1192 wrappingStyle->collapseTextDecorationProperties();
1193
1194 return wrappingStyle;
1195 }
1196
1197 auto wrappingStyle = EditingStyle::create();
1198
1199 // When not annotating for interchange, we only preserve inline style declarations.
1200 for (Node* node = &context; node && !node->isDocumentNode(); node = node->parentNode()) {
1201 if (is<StyledElement>(*node) && !isMailBlockquote(node))
1202 wrappingStyle->mergeInlineAndImplicitStyleOfElement(downcast<StyledElement>(*node), EditingStyle::DoNotOverrideValues, EditingStyle::EditingPropertiesInEffect);
1203 }
1204
1205 return wrappingStyle;
1206}
1207
1208
1209static void mergeTextDecorationValues(CSSValueList& mergedValue, const CSSValueList& valueToMerge)
1210{
1211 auto& cssValuePool = CSSValuePool::singleton();
1212 Ref<CSSPrimitiveValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline);
1213 Ref<CSSPrimitiveValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough);
1214
1215 if (valueToMerge.hasValue(underline.ptr()) && !mergedValue.hasValue(underline.ptr()))
1216 mergedValue.append(WTFMove(underline));
1217
1218 if (valueToMerge.hasValue(lineThrough.ptr()) && !mergedValue.hasValue(lineThrough.ptr()))
1219 mergedValue.append(WTFMove(lineThrough));
1220}
1221
1222void EditingStyle::mergeStyle(const StyleProperties* style, CSSPropertyOverrideMode mode)
1223{
1224 if (!style)
1225 return;
1226
1227 if (!m_mutableStyle) {
1228 m_mutableStyle = style->mutableCopy();
1229 return;
1230 }
1231
1232 unsigned propertyCount = style->propertyCount();
1233 for (unsigned i = 0; i < propertyCount; ++i) {
1234 StyleProperties::PropertyReference property = style->propertyAt(i);
1235 RefPtr<CSSValue> value = m_mutableStyle->getPropertyCSSValue(property.id());
1236
1237 // text decorations never override values.
1238 if ((property.id() == CSSPropertyTextDecoration || property.id() == CSSPropertyWebkitTextDecorationsInEffect)
1239 && is<CSSValueList>(*property.value()) && value) {
1240 if (is<CSSValueList>(*value)) {
1241 auto newValue = downcast<CSSValueList>(*value).copy();
1242 mergeTextDecorationValues(newValue, downcast<CSSValueList>(*property.value()));
1243 m_mutableStyle->setProperty(property.id(), WTFMove(newValue), property.isImportant());
1244 continue;
1245 }
1246 value = nullptr; // text-decoration: none is equivalent to not having the property.
1247 }
1248
1249 if (mode == OverrideValues || (mode == DoNotOverrideValues && !value))
1250 m_mutableStyle->setProperty(property.id(), property.value(), property.isImportant());
1251 }
1252
1253 int oldFontSizeDelta = m_fontSizeDelta;
1254 extractFontSizeDelta();
1255 m_fontSizeDelta += oldFontSizeDelta;
1256}
1257
1258static Ref<MutableStyleProperties> styleFromMatchedRulesForElement(Element& element, unsigned rulesToInclude)
1259{
1260 auto style = MutableStyleProperties::create();
1261 for (auto& matchedRule : element.styleResolver().styleRulesForElement(&element, rulesToInclude)) {
1262 if (matchedRule->isStyleRule())
1263 style->mergeAndOverrideOnConflict(static_pointer_cast<StyleRule>(matchedRule)->properties());
1264 }
1265
1266 return style;
1267}
1268
1269void EditingStyle::mergeStyleFromRules(StyledElement& element)
1270{
1271 RefPtr<MutableStyleProperties> styleFromMatchedRules = styleFromMatchedRulesForElement(element,
1272 StyleResolver::AuthorCSSRules);
1273 // Styles from the inline style declaration, held in the variable "style", take precedence
1274 // over those from matched rules.
1275 if (m_mutableStyle)
1276 styleFromMatchedRules->mergeAndOverrideOnConflict(*m_mutableStyle);
1277
1278 clear();
1279 m_mutableStyle = styleFromMatchedRules;
1280}
1281
1282void EditingStyle::mergeStyleFromRulesForSerialization(StyledElement& element)
1283{
1284 mergeStyleFromRules(element);
1285
1286 // The property value, if it's a percentage, may not reflect the actual computed value.
1287 // For example: style="height: 1%; overflow: visible;" in quirksmode
1288 // FIXME: There are others like this, see <rdar://problem/5195123> Slashdot copy/paste fidelity problem
1289 auto fromComputedStyle = MutableStyleProperties::create();
1290 ComputedStyleExtractor computedStyle(&element);
1291
1292 {
1293 unsigned propertyCount = m_mutableStyle->propertyCount();
1294 for (unsigned i = 0; i < propertyCount; ++i) {
1295 StyleProperties::PropertyReference property = m_mutableStyle->propertyAt(i);
1296 CSSValue* value = property.value();
1297 if (!is<CSSPrimitiveValue>(*value))
1298 continue;
1299 if (downcast<CSSPrimitiveValue>(*value).isPercentage()) {
1300 if (auto computedPropertyValue = computedStyle.propertyValue(property.id()))
1301 fromComputedStyle->addParsedProperty(CSSProperty(property.id(), WTFMove(computedPropertyValue)));
1302 }
1303 }
1304 }
1305 m_mutableStyle->mergeAndOverrideOnConflict(fromComputedStyle.get());
1306}
1307
1308static void removePropertiesInStyle(MutableStyleProperties* styleToRemovePropertiesFrom, MutableStyleProperties* style)
1309{
1310 unsigned propertyCount = style->propertyCount();
1311 Vector<CSSPropertyID> propertiesToRemove(propertyCount);
1312 for (unsigned i = 0; i < propertyCount; ++i)
1313 propertiesToRemove[i] = style->propertyAt(i).id();
1314
1315 styleToRemovePropertiesFrom->removePropertiesInSet(propertiesToRemove.data(), propertiesToRemove.size());
1316}
1317
1318void EditingStyle::removeStyleFromRulesAndContext(StyledElement& element, Node* context)
1319{
1320 if (!m_mutableStyle)
1321 return;
1322
1323 // 1. Remove style from matched rules because style remain without repeating it in inline style declaration
1324 RefPtr<MutableStyleProperties> styleFromMatchedRules = styleFromMatchedRulesForElement(element, StyleResolver::AllButEmptyCSSRules);
1325 if (styleFromMatchedRules && !styleFromMatchedRules->isEmpty())
1326 m_mutableStyle = getPropertiesNotIn(*m_mutableStyle, *styleFromMatchedRules);
1327
1328 // 2. Remove style present in context and not overridden by matched rules.
1329 auto computedStyle = EditingStyle::create(context, EditingPropertiesInEffect);
1330 if (computedStyle->m_mutableStyle) {
1331 if (!computedStyle->m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor))
1332 computedStyle->m_mutableStyle->setProperty(CSSPropertyBackgroundColor, CSSValueTransparent);
1333
1334 removePropertiesInStyle(computedStyle->m_mutableStyle.get(), styleFromMatchedRules.get());
1335 m_mutableStyle = getPropertiesNotIn(*m_mutableStyle, *computedStyle->m_mutableStyle);
1336 }
1337
1338 // 3. If this element is a span and has display: inline or float: none, remove them unless they are overridden by rules.
1339 // These rules are added by serialization code to wrap text nodes.
1340 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element)) {
1341 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyDisplay) && identifierForStyleProperty(*m_mutableStyle, CSSPropertyDisplay) == CSSValueInline)
1342 m_mutableStyle->removeProperty(CSSPropertyDisplay);
1343 if (!styleFromMatchedRules->getPropertyCSSValue(CSSPropertyFloat) && identifierForStyleProperty(*m_mutableStyle, CSSPropertyFloat) == CSSValueNone)
1344 m_mutableStyle->removeProperty(CSSPropertyFloat);
1345 }
1346}
1347
1348void EditingStyle::removePropertiesInElementDefaultStyle(Element& element)
1349{
1350 if (!m_mutableStyle || m_mutableStyle->isEmpty())
1351 return;
1352
1353 RefPtr<MutableStyleProperties> defaultStyle = styleFromMatchedRulesForElement(element, StyleResolver::UAAndUserCSSRules);
1354
1355 removePropertiesInStyle(m_mutableStyle.get(), defaultStyle.get());
1356}
1357
1358template<typename T>
1359void EditingStyle::removeEquivalentProperties(T& style)
1360{
1361 Vector<CSSPropertyID> propertiesToRemove;
1362 for (auto& property : m_mutableStyle->m_propertyVector) {
1363 if (style.propertyMatches(property.id(), property.value()))
1364 propertiesToRemove.append(property.id());
1365 }
1366 // FIXME: This should use mass removal.
1367 for (auto& property : propertiesToRemove)
1368 m_mutableStyle->removeProperty(property);
1369}
1370
1371void EditingStyle::forceInline()
1372{
1373 if (!m_mutableStyle)
1374 m_mutableStyle = MutableStyleProperties::create();
1375 const bool propertyIsImportant = true;
1376 m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueInline, propertyIsImportant);
1377}
1378
1379void EditingStyle::addDisplayContents()
1380{
1381 if (!m_mutableStyle)
1382 m_mutableStyle = MutableStyleProperties::create();
1383 m_mutableStyle->setProperty(CSSPropertyDisplay, CSSValueContents);
1384}
1385
1386bool EditingStyle::convertPositionStyle()
1387{
1388 if (!m_mutableStyle)
1389 return false;
1390
1391 auto& cssValuePool = CSSValuePool::singleton();
1392 RefPtr<CSSPrimitiveValue> sticky = cssValuePool.createIdentifierValue(CSSValueSticky);
1393 if (m_mutableStyle->propertyMatches(CSSPropertyPosition, sticky.get())) {
1394 m_mutableStyle->setProperty(CSSPropertyPosition, cssValuePool.createIdentifierValue(CSSValueStatic), m_mutableStyle->propertyIsImportant(CSSPropertyPosition));
1395 return false;
1396 }
1397 RefPtr<CSSPrimitiveValue> fixed = cssValuePool.createIdentifierValue(CSSValueFixed);
1398 if (m_mutableStyle->propertyMatches(CSSPropertyPosition, fixed.get())) {
1399 m_mutableStyle->setProperty(CSSPropertyPosition, cssValuePool.createIdentifierValue(CSSValueAbsolute), m_mutableStyle->propertyIsImportant(CSSPropertyPosition));
1400 return true;
1401 }
1402 RefPtr<CSSPrimitiveValue> absolute = cssValuePool.createIdentifierValue(CSSValueAbsolute);
1403 if (m_mutableStyle->propertyMatches(CSSPropertyPosition, absolute.get()))
1404 return true;
1405 return false;
1406}
1407
1408bool EditingStyle::isFloating()
1409{
1410 RefPtr<CSSValue> v = m_mutableStyle->getPropertyCSSValue(CSSPropertyFloat);
1411 RefPtr<CSSPrimitiveValue> noneValue = CSSValuePool::singleton().createIdentifierValue(CSSValueNone);
1412 return v && !v->equals(*noneValue);
1413}
1414
1415int EditingStyle::legacyFontSize(Document& document) const
1416{
1417 RefPtr<CSSValue> cssValue = m_mutableStyle->getPropertyCSSValue(CSSPropertyFontSize);
1418 if (!is<CSSPrimitiveValue>(cssValue))
1419 return 0;
1420 return legacyFontSizeFromCSSValue(document, downcast<CSSPrimitiveValue>(cssValue.get()),
1421 m_shouldUseFixedDefaultFontSize, AlwaysUseLegacyFontSize);
1422}
1423
1424bool EditingStyle::hasStyle(CSSPropertyID propertyID, const String& value)
1425{
1426 return EditingStyle::create(propertyID, value)->triStateOfStyle(this) != FalseTriState;
1427}
1428
1429RefPtr<EditingStyle> EditingStyle::styleAtSelectionStart(const VisibleSelection& selection, bool shouldUseBackgroundColorInEffect)
1430{
1431 if (selection.isNone())
1432 return nullptr;
1433
1434 Position position = adjustedSelectionStartForStyleComputation(selection);
1435
1436 // If the pos is at the end of a text node, then this node is not fully selected.
1437 // Move it to the next deep equivalent position to avoid removing the style from this node.
1438 // e.g. if pos was at Position("hello", 5) in <b>hello<div>world</div></b>, we want Position("world", 0) instead.
1439 // We only do this for range because caret at Position("hello", 5) in <b>hello</b>world should give you font-weight: bold.
1440 Node* positionNode = position.containerNode();
1441 if (selection.isRange() && positionNode && positionNode->isTextNode() && position.computeOffsetInContainerNode() == positionNode->maxCharacterOffset())
1442 position = nextVisuallyDistinctCandidate(position);
1443
1444 Element* element = position.element();
1445 if (!element)
1446 return nullptr;
1447
1448 auto style = EditingStyle::create(element, EditingStyle::AllProperties);
1449 style->mergeTypingStyle(element->document());
1450
1451 // If background color is transparent, traverse parent nodes until we hit a different value or document root
1452 // Also, if the selection is a range, ignore the background color at the start of selection,
1453 // and find the background color of the common ancestor.
1454 if (shouldUseBackgroundColorInEffect && (selection.isRange() || hasTransparentBackgroundColor(style->m_mutableStyle.get()))) {
1455 if (auto range = selection.toNormalizedRange()) {
1456 if (auto value = backgroundColorInEffect(range->commonAncestorContainer()))
1457 style->setProperty(CSSPropertyBackgroundColor, value->cssText());
1458 }
1459 }
1460
1461 return style;
1462}
1463
1464WritingDirection EditingStyle::textDirectionForSelection(const VisibleSelection& selection, EditingStyle* typingStyle, bool& hasNestedOrMultipleEmbeddings)
1465{
1466 hasNestedOrMultipleEmbeddings = true;
1467
1468 if (selection.isNone())
1469 return WritingDirection::Natural;
1470
1471 Position position = selection.start().downstream();
1472
1473 Node* node = position.deprecatedNode();
1474 if (!node)
1475 return WritingDirection::Natural;
1476
1477 Position end;
1478 if (selection.isRange()) {
1479 end = selection.end().upstream();
1480
1481 Node* pastLast = Range::create(*end.document(), position.parentAnchoredEquivalent(), end.parentAnchoredEquivalent())->pastLastNode();
1482 for (Node* n = node; n && n != pastLast; n = NodeTraversal::next(*n)) {
1483 if (!n->isStyledElement())
1484 continue;
1485
1486 RefPtr<CSSValue> unicodeBidi = ComputedStyleExtractor(n).propertyValue(CSSPropertyUnicodeBidi);
1487 if (!is<CSSPrimitiveValue>(unicodeBidi))
1488 continue;
1489
1490 CSSValueID unicodeBidiValue = downcast<CSSPrimitiveValue>(*unicodeBidi).valueID();
1491 if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride)
1492 return WritingDirection::Natural;
1493 }
1494 }
1495
1496 if (selection.isCaret()) {
1497 if (typingStyle) {
1498 if (auto direction = typingStyle->textDirection()) {
1499 hasNestedOrMultipleEmbeddings = false;
1500 return *direction;
1501 }
1502 }
1503 node = selection.visibleStart().deepEquivalent().deprecatedNode();
1504 }
1505
1506 // The selection is either a caret with no typing attributes or a range in which no embedding is added, so just use the start position
1507 // to decide.
1508 Node* block = enclosingBlock(node);
1509 auto foundDirection = WritingDirection::Natural;
1510
1511 for (; node != block; node = node->parentNode()) {
1512 if (!node->isStyledElement())
1513 continue;
1514
1515 ComputedStyleExtractor computedStyle(node);
1516 RefPtr<CSSValue> unicodeBidi = computedStyle.propertyValue(CSSPropertyUnicodeBidi);
1517 if (!is<CSSPrimitiveValue>(unicodeBidi))
1518 continue;
1519
1520 CSSValueID unicodeBidiValue = downcast<CSSPrimitiveValue>(*unicodeBidi).valueID();
1521 if (unicodeBidiValue == CSSValueNormal)
1522 continue;
1523
1524 if (unicodeBidiValue == CSSValueBidiOverride)
1525 return WritingDirection::Natural;
1526
1527 ASSERT(unicodeBidiValue == CSSValueEmbed);
1528 RefPtr<CSSValue> direction = computedStyle.propertyValue(CSSPropertyDirection);
1529 if (!is<CSSPrimitiveValue>(direction))
1530 continue;
1531
1532 CSSValueID directionValue = downcast<CSSPrimitiveValue>(*direction).valueID();
1533 if (directionValue != CSSValueLtr && directionValue != CSSValueRtl)
1534 continue;
1535
1536 if (foundDirection != WritingDirection::Natural)
1537 return WritingDirection::Natural;
1538
1539 // In the range case, make sure that the embedding element persists until the end of the range.
1540 if (selection.isRange() && !end.deprecatedNode()->isDescendantOf(*node))
1541 return WritingDirection::Natural;
1542
1543 foundDirection = directionValue == CSSValueLtr ? WritingDirection::LeftToRight : WritingDirection::RightToLeft;
1544 }
1545 hasNestedOrMultipleEmbeddings = false;
1546 return foundDirection;
1547}
1548
1549Ref<EditingStyle> EditingStyle::inverseTransformColorIfNeeded(Element& element)
1550{
1551 auto* renderer = element.renderer();
1552 if (!m_mutableStyle || !renderer || !renderer->style().hasAppleColorFilter())
1553 return *this;
1554
1555 bool hasColor = m_mutableStyle->getPropertyCSSValue(CSSPropertyColor);
1556 bool hasBackgroundColor = m_mutableStyle->getPropertyCSSValue(CSSPropertyBackgroundColor);
1557 if (!hasColor && !hasBackgroundColor)
1558 return *this;
1559
1560 auto styleWithInvertedColors = copy();
1561 ASSERT(styleWithInvertedColors->m_mutableStyle);
1562
1563 const auto& colorFilter = renderer->style().appleColorFilter();
1564 auto invertedColor = [&](CSSPropertyID propertyID) {
1565 Color newColor = cssValueToColor(extractPropertyValue(*m_mutableStyle, propertyID).get());
1566 colorFilter.inverseTransformColor(newColor);
1567 styleWithInvertedColors->m_mutableStyle->setProperty(propertyID, newColor.cssText());
1568 };
1569
1570 if (hasColor)
1571 invertedColor(CSSPropertyColor);
1572
1573 if (hasBackgroundColor)
1574 invertedColor(CSSPropertyBackgroundColor);
1575
1576 return styleWithInvertedColors;
1577}
1578
1579static void reconcileTextDecorationProperties(MutableStyleProperties* style)
1580{
1581 RefPtr<CSSValue> textDecorationsInEffect = style->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
1582 RefPtr<CSSValue> textDecoration = style->getPropertyCSSValue(CSSPropertyTextDecoration);
1583 // We shouldn't have both text-decoration and -webkit-text-decorations-in-effect because that wouldn't make sense.
1584 ASSERT(!textDecorationsInEffect || !textDecoration);
1585 if (textDecorationsInEffect) {
1586 style->setProperty(CSSPropertyTextDecoration, textDecorationsInEffect->cssText());
1587 style->removeProperty(CSSPropertyWebkitTextDecorationsInEffect);
1588 textDecoration = textDecorationsInEffect;
1589 }
1590
1591 // If text-decoration is set to "none", remove the property because we don't want to add redundant "text-decoration: none".
1592 if (textDecoration && !textDecoration->isValueList())
1593 style->removeProperty(CSSPropertyTextDecoration);
1594}
1595
1596StyleChange::StyleChange(EditingStyle* style, const Position& position)
1597 : m_applyBold(false)
1598 , m_applyItalic(false)
1599 , m_applyUnderline(false)
1600 , m_applyLineThrough(false)
1601 , m_applySubscript(false)
1602 , m_applySuperscript(false)
1603{
1604 Document* document = position.deprecatedNode() ? &position.deprecatedNode()->document() : 0;
1605 if (!style || style->isEmpty() || !document || !document->frame())
1606 return;
1607
1608 Node* node = position.containerNode();
1609 if (!node)
1610 return;
1611
1612 ComputedStyleExtractor computedStyle(node);
1613
1614 // FIXME: take care of background-color in effect
1615 RefPtr<MutableStyleProperties> mutableStyle = style->style() ?
1616 getPropertiesNotIn(*style->style(), computedStyle) : MutableStyleProperties::create();
1617
1618 reconcileTextDecorationProperties(mutableStyle.get());
1619 bool shouldStyleWithCSS = document->frame()->editor().shouldStyleWithCSS();
1620 if (!shouldStyleWithCSS)
1621 extractTextStyles(*document, *mutableStyle, computedStyle.useFixedFontDefaultSize());
1622
1623 bool shouldAddUnderline = style->underlineChange() == TextDecorationChange::Add;
1624 bool shouldAddStrikeThrough = style->strikeThroughChange() == TextDecorationChange::Add;
1625 if (shouldAddUnderline || shouldAddStrikeThrough) {
1626 RefPtr<CSSValue> value = computedStyle.propertyValue(CSSPropertyWebkitTextDecorationsInEffect);
1627 if (!is<CSSValueList>(value))
1628 value = computedStyle.propertyValue(CSSPropertyTextDecoration);
1629
1630 RefPtr<CSSValueList> valueList;
1631 if (is<CSSValueList>(value))
1632 valueList = downcast<CSSValueList>(value.get());
1633
1634 auto& cssValuePool = CSSValuePool::singleton();
1635 Ref<CSSValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline);
1636 bool hasUnderline = valueList && valueList->hasValue(underline.ptr());
1637
1638 Ref<CSSValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough);
1639 bool hasLineThrough = valueList && valueList->hasValue(lineThrough.ptr());
1640
1641 if (shouldStyleWithCSS) {
1642 valueList = valueList ? valueList->copy() : CSSValueList::createSpaceSeparated();
1643 if (shouldAddUnderline && !hasUnderline)
1644 valueList->append(WTFMove(underline));
1645 if (shouldAddStrikeThrough && !hasLineThrough)
1646 valueList->append(WTFMove(lineThrough));
1647 mutableStyle->setProperty(CSSPropertyTextDecoration, valueList.get());
1648 } else {
1649 m_applyUnderline = shouldAddUnderline && !hasUnderline;
1650 m_applyLineThrough = shouldAddStrikeThrough && !hasLineThrough;
1651 }
1652 }
1653
1654 // Changing the whitespace style in a tab span would collapse the tab into a space.
1655 if (isTabSpanTextNode(position.deprecatedNode()) || isTabSpanNode((position.deprecatedNode())))
1656 mutableStyle->removeProperty(CSSPropertyWhiteSpace);
1657
1658 // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle.
1659 // FIXME: Shouldn't this be done in getPropertiesNotIn?
1660 if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection))
1661 mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection));
1662
1663 if (!mutableStyle->isEmpty())
1664 m_cssStyle = mutableStyle;
1665}
1666
1667bool StyleChange::operator==(const StyleChange& other)
1668{
1669 if (m_applyBold != other.m_applyBold
1670 || m_applyItalic != other.m_applyItalic
1671 || m_applyUnderline != other.m_applyUnderline
1672 || m_applyLineThrough != other.m_applyLineThrough
1673 || m_applySubscript != other.m_applySubscript
1674 || m_applySuperscript != other.m_applySuperscript
1675 || m_applyFontColor != other.m_applyFontColor
1676 || m_applyFontFace != other.m_applyFontFace
1677 || m_applyFontSize != other.m_applyFontSize)
1678 return false;
1679
1680 return (!m_cssStyle && !other.m_cssStyle)
1681 || (m_cssStyle && other.m_cssStyle && m_cssStyle->asText() == other.m_cssStyle->asText());
1682}
1683
1684static void setTextDecorationProperty(MutableStyleProperties& style, const CSSValueList* newTextDecoration, CSSPropertyID propertyID)
1685{
1686 if (newTextDecoration->length())
1687 style.setProperty(propertyID, newTextDecoration->cssText(), style.propertyIsImportant(propertyID));
1688 else {
1689 // text-decoration: none is redundant since it does not remove any text decorations.
1690 style.removeProperty(propertyID);
1691 }
1692}
1693
1694void StyleChange::extractTextStyles(Document& document, MutableStyleProperties& style, bool shouldUseFixedFontDefaultSize)
1695{
1696 if (identifierForStyleProperty(style, CSSPropertyFontWeight) == CSSValueBold) {
1697 style.removeProperty(CSSPropertyFontWeight);
1698 m_applyBold = true;
1699 }
1700
1701 int fontStyle = identifierForStyleProperty(style, CSSPropertyFontStyle);
1702 if (fontStyle == CSSValueItalic || fontStyle == CSSValueOblique) {
1703 style.removeProperty(CSSPropertyFontStyle);
1704 m_applyItalic = true;
1705 }
1706
1707 // Assuming reconcileTextDecorationProperties has been called, there should not be -webkit-text-decorations-in-effect
1708 // Furthermore, text-decoration: none has been trimmed so that text-decoration property is always a CSSValueList.
1709 RefPtr<CSSValue> textDecoration = style.getPropertyCSSValue(CSSPropertyTextDecoration);
1710 if (is<CSSValueList>(textDecoration)) {
1711 auto& cssValuePool = CSSValuePool::singleton();
1712 RefPtr<CSSPrimitiveValue> underline = cssValuePool.createIdentifierValue(CSSValueUnderline);
1713 RefPtr<CSSPrimitiveValue> lineThrough = cssValuePool.createIdentifierValue(CSSValueLineThrough);
1714
1715 RefPtr<CSSValueList> newTextDecoration = downcast<CSSValueList>(*textDecoration).copy();
1716 if (newTextDecoration->removeAll(underline.get()))
1717 m_applyUnderline = true;
1718 if (newTextDecoration->removeAll(lineThrough.get()))
1719 m_applyLineThrough = true;
1720
1721 // If trimTextDecorations, delete underline and line-through
1722 setTextDecorationProperty(style, newTextDecoration.get(), CSSPropertyTextDecoration);
1723 }
1724
1725 int verticalAlign = identifierForStyleProperty(style, CSSPropertyVerticalAlign);
1726 switch (verticalAlign) {
1727 case CSSValueSub:
1728 style.removeProperty(CSSPropertyVerticalAlign);
1729 m_applySubscript = true;
1730 break;
1731 case CSSValueSuper:
1732 style.removeProperty(CSSPropertyVerticalAlign);
1733 m_applySuperscript = true;
1734 break;
1735 }
1736
1737 if (style.getPropertyCSSValue(CSSPropertyColor)) {
1738 auto color = textColorFromStyle(style);
1739 if (color.isOpaque()) {
1740 m_applyFontColor = color.serialized();
1741 style.removeProperty(CSSPropertyColor);
1742 }
1743 }
1744
1745 m_applyFontFace = style.getPropertyValue(CSSPropertyFontFamily);
1746 // Remove quotes for Outlook 2007 compatibility. See https://bugs.webkit.org/show_bug.cgi?id=79448
1747 m_applyFontFace.replaceWithLiteral('\"', "");
1748 style.removeProperty(CSSPropertyFontFamily);
1749
1750 if (RefPtr<CSSValue> fontSize = style.getPropertyCSSValue(CSSPropertyFontSize)) {
1751 if (!is<CSSPrimitiveValue>(*fontSize))
1752 style.removeProperty(CSSPropertyFontSize); // Can't make sense of the number. Put no font size.
1753 else if (int legacyFontSize = legacyFontSizeFromCSSValue(document, downcast<CSSPrimitiveValue>(fontSize.get()),
1754 shouldUseFixedFontDefaultSize, UseLegacyFontSizeOnlyIfPixelValuesMatch)) {
1755 m_applyFontSize = String::number(legacyFontSize);
1756 style.removeProperty(CSSPropertyFontSize);
1757 }
1758 }
1759}
1760
1761static void diffTextDecorations(MutableStyleProperties& style, CSSPropertyID propertID, CSSValue* refTextDecoration)
1762{
1763 RefPtr<CSSValue> textDecoration = style.getPropertyCSSValue(propertID);
1764 if (!is<CSSValueList>(textDecoration) || !is<CSSValueList>(refTextDecoration))
1765 return;
1766
1767 RefPtr<CSSValueList> newTextDecoration = downcast<CSSValueList>(*textDecoration).copy();
1768
1769 for (auto& value : downcast<CSSValueList>(*refTextDecoration))
1770 newTextDecoration->removeAll(&value.get());
1771
1772 setTextDecorationProperty(style, newTextDecoration.get(), propertID);
1773}
1774
1775static bool fontWeightIsBold(CSSValue& fontWeight)
1776{
1777 if (!is<CSSPrimitiveValue>(fontWeight))
1778 return false;
1779
1780 auto& primitiveValue = downcast<CSSPrimitiveValue>(fontWeight);
1781 switch (primitiveValue.valueID()) {
1782 case CSSValueNormal:
1783 return false;
1784 case CSSValueBold:
1785 return true;
1786 default:
1787 break;
1788 }
1789
1790 ASSERT(primitiveValue.isNumber());
1791 return primitiveValue.floatValue() >= static_cast<float>(boldThreshold());
1792}
1793
1794template<typename T>
1795static bool fontWeightIsBold(T& style)
1796{
1797 RefPtr<CSSValue> fontWeight = extractPropertyValue(style, CSSPropertyFontWeight);
1798 return fontWeight && fontWeightIsBold(*fontWeight);
1799}
1800
1801template<typename T>
1802static Ref<MutableStyleProperties> extractPropertiesNotIn(StyleProperties& styleWithRedundantProperties, T& baseStyle)
1803{
1804 auto result = EditingStyle::create(&styleWithRedundantProperties);
1805 result->removeEquivalentProperties(baseStyle);
1806 ASSERT(result->style());
1807 Ref<MutableStyleProperties> mutableStyle = *result->style();
1808
1809 RefPtr<CSSValue> baseTextDecorationsInEffect = extractPropertyValue(baseStyle, CSSPropertyWebkitTextDecorationsInEffect);
1810 diffTextDecorations(mutableStyle, CSSPropertyTextDecoration, baseTextDecorationsInEffect.get());
1811 diffTextDecorations(mutableStyle, CSSPropertyWebkitTextDecorationsInEffect, baseTextDecorationsInEffect.get());
1812
1813 if (extractPropertyValue(baseStyle, CSSPropertyFontWeight) && fontWeightIsBold(mutableStyle) == fontWeightIsBold(baseStyle))
1814 mutableStyle->removeProperty(CSSPropertyFontWeight);
1815
1816 if (extractPropertyValue(baseStyle, CSSPropertyColor) && equalIgnoringSemanticColor(textColorFromStyle(mutableStyle), textColorFromStyle(baseStyle)))
1817 mutableStyle->removeProperty(CSSPropertyColor);
1818
1819 if (extractPropertyValue(baseStyle, CSSPropertyCaretColor) && equalIgnoringSemanticColor(caretColorFromStyle(mutableStyle), caretColorFromStyle(baseStyle)))
1820 mutableStyle->removeProperty(CSSPropertyCaretColor);
1821
1822 if (extractPropertyValue(baseStyle, CSSPropertyTextAlign)
1823 && textAlignResolvingStartAndEnd(mutableStyle) == textAlignResolvingStartAndEnd(baseStyle))
1824 mutableStyle->removeProperty(CSSPropertyTextAlign);
1825
1826 if (extractPropertyValue(baseStyle, CSSPropertyBackgroundColor) && equalIgnoringSemanticColor(backgroundColorFromStyle(mutableStyle), backgroundColorFromStyle(baseStyle)))
1827 mutableStyle->removeProperty(CSSPropertyBackgroundColor);
1828
1829 return mutableStyle;
1830}
1831
1832template<typename T>
1833Ref<MutableStyleProperties> getPropertiesNotIn(StyleProperties& styleWithRedundantProperties, T& baseStyle)
1834{
1835 return extractPropertiesNotIn(styleWithRedundantProperties, baseStyle);
1836}
1837
1838static bool isCSSValueLength(CSSPrimitiveValue* value)
1839{
1840 return value->isFontIndependentLength();
1841}
1842
1843int legacyFontSizeFromCSSValue(Document& document, CSSPrimitiveValue* value, bool shouldUseFixedFontDefaultSize, LegacyFontSizeMode mode)
1844{
1845 if (isCSSValueLength(value)) {
1846 int pixelFontSize = value->intValue(CSSPrimitiveValue::CSS_PX);
1847 int legacyFontSize = Style::legacyFontSizeForPixelSize(pixelFontSize, shouldUseFixedFontDefaultSize, document);
1848 // Use legacy font size only if pixel value matches exactly to that of legacy font size.
1849 int cssPrimitiveEquivalent = legacyFontSize - 1 + CSSValueXSmall;
1850 if (mode == AlwaysUseLegacyFontSize || Style::fontSizeForKeyword(cssPrimitiveEquivalent, shouldUseFixedFontDefaultSize, document) == pixelFontSize)
1851 return legacyFontSize;
1852
1853 return 0;
1854 }
1855
1856 if (CSSValueXSmall <= value->valueID() && value->valueID() <= CSSValueWebkitXxxLarge)
1857 return value->valueID() - CSSValueXSmall + 1;
1858
1859 return 0;
1860}
1861
1862static bool isTransparentColorValue(CSSValue* value)
1863{
1864 if (!value)
1865 return true;
1866 if (!is<CSSPrimitiveValue>(*value))
1867 return false;
1868 auto& primitiveValue = downcast<CSSPrimitiveValue>(*value);
1869 if (primitiveValue.isRGBColor())
1870 return !primitiveValue.color().isVisible();
1871 return primitiveValue.valueID() == CSSValueTransparent;
1872}
1873
1874bool hasTransparentBackgroundColor(StyleProperties* style)
1875{
1876 return isTransparentColorValue(style->getPropertyCSSValue(CSSPropertyBackgroundColor).get());
1877}
1878
1879RefPtr<CSSValue> backgroundColorInEffect(Node* node)
1880{
1881 for (Node* ancestor = node; ancestor; ancestor = ancestor->parentNode()) {
1882 if (auto value = ComputedStyleExtractor(ancestor).propertyValue(CSSPropertyBackgroundColor)) {
1883 if (!isTransparentColorValue(value.get()))
1884 return value;
1885 }
1886 }
1887 return nullptr;
1888}
1889
1890}
1891