1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "CSSEditUtils.h"
8 #include "ChangeStyleTransaction.h"
9 #include "HTMLEditHelpers.h"
10 #include "HTMLEditor.h"
11 #include "HTMLEditUtils.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/DeclarationBlock.h"
15 #include "mozilla/mozalloc.h"
16 #include "mozilla/Preferences.h"
17 #include "mozilla/ServoCSSParser.h"
18 #include "mozilla/StaticPrefs_editor.h"
19 #include "mozilla/dom/Document.h"
20 #include "mozilla/dom/Element.h"
21 #include "nsAString.h"
23 #include "nsCSSProps.h"
25 #include "nsComputedDOMStyle.h"
27 #include "nsDependentSubstring.h"
29 #include "nsGkAtoms.h"
31 #include "nsIContent.h"
32 #include "nsICSSDeclaration.h"
34 #include "nsISupportsImpl.h"
35 #include "nsISupportsUtils.h"
36 #include "nsLiteralString.h"
37 #include "nsPIDOMWindow.h"
38 #include "nsReadableUtils.h"
40 #include "nsStringFwd.h"
41 #include "nsStringIterator.h"
42 #include "nsStyledElement.h"
43 #include "nsUnicharUtils.h"
49 static void ProcessBValue(const nsAString
* aInputString
,
50 nsAString
& aOutputString
,
51 const char* aDefaultValueString
,
52 const char* aPrependString
,
53 const char* aAppendString
) {
54 if (aInputString
&& aInputString
->EqualsLiteral("-moz-editor-invert-value")) {
55 aOutputString
.AssignLiteral("normal");
57 aOutputString
.AssignLiteral("bold");
61 static void ProcessDefaultValue(const nsAString
* aInputString
,
62 nsAString
& aOutputString
,
63 const char* aDefaultValueString
,
64 const char* aPrependString
,
65 const char* aAppendString
) {
66 CopyASCIItoUTF16(MakeStringSpan(aDefaultValueString
), aOutputString
);
69 static void ProcessSameValue(const nsAString
* aInputString
,
70 nsAString
& aOutputString
,
71 const char* aDefaultValueString
,
72 const char* aPrependString
,
73 const char* aAppendString
) {
75 aOutputString
.Assign(*aInputString
);
77 aOutputString
.Truncate();
80 static void ProcessExtendedValue(const nsAString
* aInputString
,
81 nsAString
& aOutputString
,
82 const char* aDefaultValueString
,
83 const char* aPrependString
,
84 const char* aAppendString
) {
85 aOutputString
.Truncate();
88 AppendASCIItoUTF16(MakeStringSpan(aPrependString
), aOutputString
);
90 aOutputString
.Append(*aInputString
);
92 AppendASCIItoUTF16(MakeStringSpan(aAppendString
), aOutputString
);
97 static void ProcessLengthValue(const nsAString
* aInputString
,
98 nsAString
& aOutputString
,
99 const char* aDefaultValueString
,
100 const char* aPrependString
,
101 const char* aAppendString
) {
102 aOutputString
.Truncate();
104 aOutputString
.Append(*aInputString
);
105 if (-1 == aOutputString
.FindChar(char16_t('%'))) {
106 aOutputString
.AppendLiteral("px");
111 static void ProcessListStyleTypeValue(const nsAString
* aInputString
,
112 nsAString
& aOutputString
,
113 const char* aDefaultValueString
,
114 const char* aPrependString
,
115 const char* aAppendString
) {
116 aOutputString
.Truncate();
118 if (aInputString
->EqualsLiteral("1")) {
119 aOutputString
.AppendLiteral("decimal");
120 } else if (aInputString
->EqualsLiteral("a")) {
121 aOutputString
.AppendLiteral("lower-alpha");
122 } else if (aInputString
->EqualsLiteral("A")) {
123 aOutputString
.AppendLiteral("upper-alpha");
124 } else if (aInputString
->EqualsLiteral("i")) {
125 aOutputString
.AppendLiteral("lower-roman");
126 } else if (aInputString
->EqualsLiteral("I")) {
127 aOutputString
.AppendLiteral("upper-roman");
128 } else if (aInputString
->EqualsLiteral("square") ||
129 aInputString
->EqualsLiteral("circle") ||
130 aInputString
->EqualsLiteral("disc")) {
131 aOutputString
.Append(*aInputString
);
136 static void ProcessMarginLeftValue(const nsAString
* aInputString
,
137 nsAString
& aOutputString
,
138 const char* aDefaultValueString
,
139 const char* aPrependString
,
140 const char* aAppendString
) {
141 aOutputString
.Truncate();
143 if (aInputString
->EqualsLiteral("center") ||
144 aInputString
->EqualsLiteral("-moz-center")) {
145 aOutputString
.AppendLiteral("auto");
146 } else if (aInputString
->EqualsLiteral("right") ||
147 aInputString
->EqualsLiteral("-moz-right")) {
148 aOutputString
.AppendLiteral("auto");
150 aOutputString
.AppendLiteral("0px");
155 static void ProcessMarginRightValue(const nsAString
* aInputString
,
156 nsAString
& aOutputString
,
157 const char* aDefaultValueString
,
158 const char* aPrependString
,
159 const char* aAppendString
) {
160 aOutputString
.Truncate();
162 if (aInputString
->EqualsLiteral("center") ||
163 aInputString
->EqualsLiteral("-moz-center")) {
164 aOutputString
.AppendLiteral("auto");
165 } else if (aInputString
->EqualsLiteral("left") ||
166 aInputString
->EqualsLiteral("-moz-left")) {
167 aOutputString
.AppendLiteral("auto");
169 aOutputString
.AppendLiteral("0px");
174 #define CSS_EQUIV_TABLE_NONE {CSSEditUtils::eCSSEditableProperty_NONE, 0}
176 const CSSEditUtils::CSSEquivTable boldEquivTable
[] = {
177 {CSSEditUtils::eCSSEditableProperty_font_weight
, true, false, ProcessBValue
,
178 nullptr, nullptr, nullptr},
179 CSS_EQUIV_TABLE_NONE
};
181 const CSSEditUtils::CSSEquivTable italicEquivTable
[] = {
182 {CSSEditUtils::eCSSEditableProperty_font_style
, true, false,
183 ProcessDefaultValue
, "italic", nullptr, nullptr},
184 CSS_EQUIV_TABLE_NONE
};
186 const CSSEditUtils::CSSEquivTable underlineEquivTable
[] = {
187 {CSSEditUtils::eCSSEditableProperty_text_decoration
, true, false,
188 ProcessDefaultValue
, "underline", nullptr, nullptr},
189 CSS_EQUIV_TABLE_NONE
};
191 const CSSEditUtils::CSSEquivTable strikeEquivTable
[] = {
192 {CSSEditUtils::eCSSEditableProperty_text_decoration
, true, false,
193 ProcessDefaultValue
, "line-through", nullptr, nullptr},
194 CSS_EQUIV_TABLE_NONE
};
196 const CSSEditUtils::CSSEquivTable ttEquivTable
[] = {
197 {CSSEditUtils::eCSSEditableProperty_font_family
, true, false,
198 ProcessDefaultValue
, "monospace", nullptr, nullptr},
199 CSS_EQUIV_TABLE_NONE
};
201 const CSSEditUtils::CSSEquivTable fontColorEquivTable
[] = {
202 {CSSEditUtils::eCSSEditableProperty_color
, true, false, ProcessSameValue
,
203 nullptr, nullptr, nullptr},
204 CSS_EQUIV_TABLE_NONE
};
206 const CSSEditUtils::CSSEquivTable fontFaceEquivTable
[] = {
207 {CSSEditUtils::eCSSEditableProperty_font_family
, true, false,
208 ProcessSameValue
, nullptr, nullptr, nullptr},
209 CSS_EQUIV_TABLE_NONE
};
211 const CSSEditUtils::CSSEquivTable fontSizeEquivTable
[] = {
212 {CSSEditUtils::eCSSEditableProperty_font_size
, true, false,
213 ProcessSameValue
, nullptr, nullptr, nullptr},
214 CSS_EQUIV_TABLE_NONE
};
216 const CSSEditUtils::CSSEquivTable bgcolorEquivTable
[] = {
217 {CSSEditUtils::eCSSEditableProperty_background_color
, true, false,
218 ProcessSameValue
, nullptr, nullptr, nullptr},
219 CSS_EQUIV_TABLE_NONE
};
221 const CSSEditUtils::CSSEquivTable backgroundImageEquivTable
[] = {
222 {CSSEditUtils::eCSSEditableProperty_background_image
, true, true,
223 ProcessExtendedValue
, nullptr, "url(", ")"},
224 CSS_EQUIV_TABLE_NONE
};
226 const CSSEditUtils::CSSEquivTable textColorEquivTable
[] = {
227 {CSSEditUtils::eCSSEditableProperty_color
, true, false, ProcessSameValue
,
228 nullptr, nullptr, nullptr},
229 CSS_EQUIV_TABLE_NONE
};
231 const CSSEditUtils::CSSEquivTable borderEquivTable
[] = {
232 {CSSEditUtils::eCSSEditableProperty_border
, true, false,
233 ProcessExtendedValue
, nullptr, nullptr, "px solid"},
234 CSS_EQUIV_TABLE_NONE
};
236 const CSSEditUtils::CSSEquivTable textAlignEquivTable
[] = {
237 {CSSEditUtils::eCSSEditableProperty_text_align
, true, false,
238 ProcessSameValue
, nullptr, nullptr, nullptr},
239 CSS_EQUIV_TABLE_NONE
};
241 const CSSEditUtils::CSSEquivTable captionAlignEquivTable
[] = {
242 {CSSEditUtils::eCSSEditableProperty_caption_side
, true, false,
243 ProcessSameValue
, nullptr, nullptr, nullptr},
244 CSS_EQUIV_TABLE_NONE
};
246 const CSSEditUtils::CSSEquivTable verticalAlignEquivTable
[] = {
247 {CSSEditUtils::eCSSEditableProperty_vertical_align
, true, false,
248 ProcessSameValue
, nullptr, nullptr, nullptr},
249 CSS_EQUIV_TABLE_NONE
};
251 const CSSEditUtils::CSSEquivTable nowrapEquivTable
[] = {
252 {CSSEditUtils::eCSSEditableProperty_whitespace
, true, false,
253 ProcessDefaultValue
, "nowrap", nullptr, nullptr},
254 CSS_EQUIV_TABLE_NONE
};
256 const CSSEditUtils::CSSEquivTable widthEquivTable
[] = {
257 {CSSEditUtils::eCSSEditableProperty_width
, true, false, ProcessLengthValue
,
258 nullptr, nullptr, nullptr},
259 CSS_EQUIV_TABLE_NONE
};
261 const CSSEditUtils::CSSEquivTable heightEquivTable
[] = {
262 {CSSEditUtils::eCSSEditableProperty_height
, true, false, ProcessLengthValue
,
263 nullptr, nullptr, nullptr},
264 CSS_EQUIV_TABLE_NONE
};
266 const CSSEditUtils::CSSEquivTable listStyleTypeEquivTable
[] = {
267 {CSSEditUtils::eCSSEditableProperty_list_style_type
, true, true,
268 ProcessListStyleTypeValue
, nullptr, nullptr, nullptr},
269 CSS_EQUIV_TABLE_NONE
};
271 const CSSEditUtils::CSSEquivTable tableAlignEquivTable
[] = {
272 {CSSEditUtils::eCSSEditableProperty_text_align
, false, false,
273 ProcessDefaultValue
, "left", nullptr, nullptr},
274 {CSSEditUtils::eCSSEditableProperty_margin_left
, true, false,
275 ProcessMarginLeftValue
, nullptr, nullptr, nullptr},
276 {CSSEditUtils::eCSSEditableProperty_margin_right
, true, false,
277 ProcessMarginRightValue
, nullptr, nullptr, nullptr},
278 CSS_EQUIV_TABLE_NONE
};
280 const CSSEditUtils::CSSEquivTable hrAlignEquivTable
[] = {
281 {CSSEditUtils::eCSSEditableProperty_margin_left
, true, false,
282 ProcessMarginLeftValue
, nullptr, nullptr, nullptr},
283 {CSSEditUtils::eCSSEditableProperty_margin_right
, true, false,
284 ProcessMarginRightValue
, nullptr, nullptr, nullptr},
285 CSS_EQUIV_TABLE_NONE
};
287 #undef CSS_EQUIV_TABLE_NONE
290 bool CSSEditUtils::IsCSSEditableStyle(const Element
& aElement
,
291 const EditorElementStyle
& aStyle
) {
292 return CSSEditUtils::IsCSSEditableStyle(*aElement
.NodeInfo()->NameAtom(),
297 bool CSSEditUtils::IsCSSEditableStyle(const nsAtom
& aTagName
,
298 const EditorElementStyle
& aStyle
) {
299 nsStaticAtom
* const htmlProperty
=
300 aStyle
.IsInlineStyle() ? aStyle
.AsInlineStyle().mHTMLProperty
: nullptr;
301 nsAtom
* const attributeOrStyle
= aStyle
.IsInlineStyle()
302 ? aStyle
.AsInlineStyle().mAttribute
.get()
305 // HTML inline styles <b>, <i>, <tt> (chrome only), <u>, <strike>, <font
306 // color> and <font style>.
307 if (nsGkAtoms::b
== htmlProperty
|| nsGkAtoms::i
== htmlProperty
||
308 nsGkAtoms::tt
== htmlProperty
|| nsGkAtoms::u
== htmlProperty
||
309 nsGkAtoms::strike
== htmlProperty
||
310 (nsGkAtoms::font
== htmlProperty
&&
311 (attributeOrStyle
== nsGkAtoms::color
||
312 attributeOrStyle
== nsGkAtoms::face
))) {
316 // ALIGN attribute on elements supporting it
317 if (attributeOrStyle
== nsGkAtoms::align
&&
318 (&aTagName
== nsGkAtoms::div
|| &aTagName
== nsGkAtoms::p
||
319 &aTagName
== nsGkAtoms::h1
|| &aTagName
== nsGkAtoms::h2
||
320 &aTagName
== nsGkAtoms::h3
|| &aTagName
== nsGkAtoms::h4
||
321 &aTagName
== nsGkAtoms::h5
|| &aTagName
== nsGkAtoms::h6
||
322 &aTagName
== nsGkAtoms::td
|| &aTagName
== nsGkAtoms::th
||
323 &aTagName
== nsGkAtoms::table
|| &aTagName
== nsGkAtoms::hr
||
324 // For the above, why not use
325 // HTMLEditUtils::SupportsAlignAttr?
326 // It also checks for tbody, tfoot, thead.
327 // Let's add the following elements here even
328 // if "align" has a different meaning for them
329 &aTagName
== nsGkAtoms::legend
|| &aTagName
== nsGkAtoms::caption
)) {
333 if (attributeOrStyle
== nsGkAtoms::valign
&&
334 (&aTagName
== nsGkAtoms::col
|| &aTagName
== nsGkAtoms::colgroup
||
335 &aTagName
== nsGkAtoms::tbody
|| &aTagName
== nsGkAtoms::td
||
336 &aTagName
== nsGkAtoms::th
|| &aTagName
== nsGkAtoms::tfoot
||
337 &aTagName
== nsGkAtoms::thead
|| &aTagName
== nsGkAtoms::tr
)) {
341 // attributes TEXT, BACKGROUND and BGCOLOR on <body>
342 if (&aTagName
== nsGkAtoms::body
&&
343 (attributeOrStyle
== nsGkAtoms::text
||
344 attributeOrStyle
== nsGkAtoms::background
||
345 attributeOrStyle
== nsGkAtoms::bgcolor
)) {
349 // attribute BGCOLOR on other elements
350 if (attributeOrStyle
== nsGkAtoms::bgcolor
) {
354 // attributes HEIGHT, WIDTH and NOWRAP on <td> and <th>
355 if ((&aTagName
== nsGkAtoms::td
|| &aTagName
== nsGkAtoms::th
) &&
356 (attributeOrStyle
== nsGkAtoms::height
||
357 attributeOrStyle
== nsGkAtoms::width
||
358 attributeOrStyle
== nsGkAtoms::nowrap
)) {
362 // attributes HEIGHT and WIDTH on <table>
363 if (&aTagName
== nsGkAtoms::table
&& (attributeOrStyle
== nsGkAtoms::height
||
364 attributeOrStyle
== nsGkAtoms::width
)) {
368 // attributes SIZE and WIDTH on <hr>
369 if (&aTagName
== nsGkAtoms::hr
&& (attributeOrStyle
== nsGkAtoms::size
||
370 attributeOrStyle
== nsGkAtoms::width
)) {
374 // attribute TYPE on <ol>, <ul> and <li>
375 if (attributeOrStyle
== nsGkAtoms::type
&&
376 (&aTagName
== nsGkAtoms::ol
|| &aTagName
== nsGkAtoms::ul
||
377 &aTagName
== nsGkAtoms::li
)) {
381 if (&aTagName
== nsGkAtoms::img
&& (attributeOrStyle
== nsGkAtoms::border
||
382 attributeOrStyle
== nsGkAtoms::width
||
383 attributeOrStyle
== nsGkAtoms::height
)) {
387 // other elements that we can align using CSS even if they
388 // can't carry the html ALIGN attribute
389 if (attributeOrStyle
== nsGkAtoms::align
&&
390 (&aTagName
== nsGkAtoms::ul
|| &aTagName
== nsGkAtoms::ol
||
391 &aTagName
== nsGkAtoms::dl
|| &aTagName
== nsGkAtoms::li
||
392 &aTagName
== nsGkAtoms::dd
|| &aTagName
== nsGkAtoms::dt
||
393 &aTagName
== nsGkAtoms::address
|| &aTagName
== nsGkAtoms::pre
)) {
400 // The lowest level above the transaction; adds the CSS declaration
401 // "aProperty : aValue" to the inline styles carried by aStyledElement
404 nsresult
CSSEditUtils::SetCSSPropertyInternal(HTMLEditor
& aHTMLEditor
,
405 nsStyledElement
& aStyledElement
,
407 const nsAString
& aValue
,
409 RefPtr
<ChangeStyleTransaction
> transaction
=
410 ChangeStyleTransaction::Create(aStyledElement
, aProperty
, aValue
);
412 nsresult rv
= transaction
->DoTransaction();
413 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
414 "ChangeStyleTransaction::DoTransaction() failed");
417 nsresult rv
= aHTMLEditor
.DoTransactionInternal(transaction
);
418 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
419 return NS_ERROR_EDITOR_DESTROYED
;
421 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
422 "EditorBase::DoTransactionInternal() failed");
427 nsresult
CSSEditUtils::SetCSSPropertyPixelsWithTransaction(
428 HTMLEditor
& aHTMLEditor
, nsStyledElement
& aStyledElement
, nsAtom
& aProperty
,
431 s
.AppendInt(aIntValue
);
432 nsresult rv
= SetCSSPropertyWithTransaction(aHTMLEditor
, aStyledElement
,
433 aProperty
, s
+ u
"px"_ns
);
434 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
435 "CSSEditUtils::SetCSSPropertyWithTransaction() failed");
440 nsresult
CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(
441 nsStyledElement
& aStyledElement
, const nsAtom
& aProperty
,
443 nsCOMPtr
<nsICSSDeclaration
> cssDecl
= aStyledElement
.Style();
445 nsAutoCString propertyNameString
;
446 aProperty
.ToUTF8String(propertyNameString
);
449 s
.AppendInt(aIntValue
);
450 s
.AppendLiteral("px");
453 cssDecl
->SetProperty(propertyNameString
, s
, EmptyCString(), error
);
454 if (error
.Failed()) {
455 NS_WARNING("nsICSSDeclaration::SetProperty() failed");
456 return error
.StealNSResult();
462 // The lowest level above the transaction; removes the value aValue from the
463 // list of values specified for the CSS property aProperty, or totally remove
464 // the declaration if this property accepts only one value
467 nsresult
CSSEditUtils::RemoveCSSPropertyInternal(
468 HTMLEditor
& aHTMLEditor
, nsStyledElement
& aStyledElement
, nsAtom
& aProperty
,
469 const nsAString
& aValue
, bool aSuppressTxn
) {
470 RefPtr
<ChangeStyleTransaction
> transaction
=
471 ChangeStyleTransaction::CreateToRemove(aStyledElement
, aProperty
, aValue
);
473 nsresult rv
= transaction
->DoTransaction();
474 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
475 "ChangeStyleTransaction::DoTransaction() failed");
478 nsresult rv
= aHTMLEditor
.DoTransactionInternal(transaction
);
479 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
480 return NS_ERROR_EDITOR_DESTROYED
;
482 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
483 "EditorBase::DoTransactionInternal() failed");
488 nsresult
CSSEditUtils::GetSpecifiedProperty(nsIContent
& aContent
,
489 nsAtom
& aCSSProperty
,
492 GetSpecifiedCSSInlinePropertyBase(aContent
, aCSSProperty
, aValue
);
493 NS_WARNING_ASSERTION(
495 "CSSEditUtils::GeSpecifiedCSSInlinePropertyBase() failed");
500 nsresult
CSSEditUtils::GetComputedProperty(nsIContent
& aContent
,
501 nsAtom
& aCSSProperty
,
504 GetComputedCSSInlinePropertyBase(aContent
, aCSSProperty
, aValue
);
505 NS_WARNING_ASSERTION(
507 "CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
512 nsresult
CSSEditUtils::GetComputedCSSInlinePropertyBase(nsIContent
& aContent
,
513 nsAtom
& aCSSProperty
,
517 RefPtr
<Element
> element
= aContent
.GetAsElementOrParentElement();
518 if (NS_WARN_IF(!element
)) {
519 return NS_ERROR_INVALID_ARG
;
522 // Get the all the computed css styles attached to the element node
523 RefPtr
<nsComputedDOMStyle
> computedDOMStyle
= GetComputedStyle(element
);
524 if (NS_WARN_IF(!computedDOMStyle
)) {
525 return NS_ERROR_INVALID_ARG
;
528 // from these declarations, get the one we want and that one only
530 // FIXME(bug 1606994): nsAtomCString copies, we should just keep around the
533 // FIXME: Maybe we can avoid copying aValue too, though it's no worse than
534 // what we used to do.
536 computedDOMStyle
->GetPropertyValue(nsAtomCString(&aCSSProperty
), value
);
537 CopyUTF8toUTF16(value
, aValue
);
542 nsresult
CSSEditUtils::GetSpecifiedCSSInlinePropertyBase(nsIContent
& aContent
,
543 nsAtom
& aCSSProperty
,
547 RefPtr
<Element
> element
= aContent
.GetAsElementOrParentElement();
548 if (NS_WARN_IF(!element
)) {
549 return NS_ERROR_INVALID_ARG
;
552 RefPtr
<DeclarationBlock
> decl
= element
->GetInlineStyleDeclaration();
557 // FIXME: Same comments as above.
558 nsCSSPropertyID prop
=
559 nsCSSProps::LookupProperty(nsAtomCString(&aCSSProperty
));
560 MOZ_ASSERT(prop
!= eCSSProperty_UNKNOWN
);
563 decl
->GetPropertyValueByID(prop
, value
);
564 CopyUTF8toUTF16(value
, aValue
);
569 already_AddRefed
<nsComputedDOMStyle
> CSSEditUtils::GetComputedStyle(
571 MOZ_ASSERT(aElement
);
573 Document
* document
= aElement
->GetComposedDoc();
574 if (NS_WARN_IF(!document
)) {
578 RefPtr
<nsComputedDOMStyle
> computedDOMStyle
= NS_NewComputedDOMStyle(
579 aElement
, u
""_ns
, document
, nsComputedDOMStyle::StyleType::All
,
581 return computedDOMStyle
.forget();
584 // remove the CSS style "aProperty : aPropertyValue" and possibly remove the
585 // whole node if it is a span and if its only attribute is _moz_dirty
588 Result
<EditorDOMPoint
, nsresult
>
589 CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
590 HTMLEditor
& aHTMLEditor
, nsStyledElement
& aStyledElement
, nsAtom
* aProperty
,
591 const nsAString
& aPropertyValue
) {
592 // remove the property from the style attribute
593 nsresult rv
= RemoveCSSPropertyWithTransaction(aHTMLEditor
, aStyledElement
,
594 *aProperty
, aPropertyValue
);
596 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
600 if (!aStyledElement
.IsHTMLElement(nsGkAtoms::span
) ||
601 HTMLEditUtils::ElementHasAttribute(aStyledElement
)) {
602 return EditorDOMPoint();
605 Result
<EditorDOMPoint
, nsresult
> unwrapStyledElementResult
=
606 aHTMLEditor
.RemoveContainerWithTransaction(aStyledElement
);
607 NS_WARNING_ASSERTION(unwrapStyledElementResult
.isOk(),
608 "HTMLEditor::RemoveContainerWithTransaction() failed");
609 return unwrapStyledElementResult
;
612 // Get the default browser background color if we need it for
613 // GetCSSBackgroundColorState
616 void CSSEditUtils::GetDefaultBackgroundColor(nsAString
& aColor
) {
617 if (MOZ_UNLIKELY(StaticPrefs::editor_use_custom_colors())) {
618 nsresult rv
= Preferences::GetString("editor.background_color", aColor
);
619 // XXX Why don't you validate the pref value?
621 NS_WARNING("failed to get editor.background_color");
622 aColor
.AssignLiteral("#ffffff"); // Default to white
627 if (Preferences::GetBool("browser.display.use_system_colors", false)) {
632 Preferences::GetString("browser.display.background_color", aColor
);
633 // XXX Why don't you validate the pref value?
635 NS_WARNING("failed to get browser.display.background_color");
636 aColor
.AssignLiteral("#ffffff"); // Default to white
641 void CSSEditUtils::ParseLength(const nsAString
& aString
, float* aValue
,
643 if (aString
.IsEmpty()) {
645 *aUnit
= NS_Atomize(aString
).take();
649 nsAString::const_iterator iter
;
650 aString
.BeginReading(iter
);
652 float a
= 10.0f
, b
= 1.0f
, value
= 0;
654 int32_t i
= 0, j
= aString
.Length();
656 bool floatingPointFound
= false;
658 if (char16_t('-') == c
) {
662 } else if (char16_t('+') == c
) {
668 if ((char16_t('0') == c
) || (char16_t('1') == c
) || (char16_t('2') == c
) ||
669 (char16_t('3') == c
) || (char16_t('4') == c
) || (char16_t('5') == c
) ||
670 (char16_t('6') == c
) || (char16_t('7') == c
) || (char16_t('8') == c
) ||
671 (char16_t('9') == c
)) {
672 value
= (value
* a
) + (b
* (c
- char16_t('0')));
674 } else if (!floatingPointFound
&& (char16_t('.') == c
)) {
675 floatingPointFound
= true;
683 *aValue
= value
* sign
;
684 *aUnit
= NS_Atomize(StringTail(aString
, j
- i
)).take();
688 nsStaticAtom
* CSSEditUtils::GetCSSPropertyAtom(
689 nsCSSEditableProperty aProperty
) {
691 case eCSSEditableProperty_background_color
:
692 return nsGkAtoms::backgroundColor
;
693 case eCSSEditableProperty_background_image
:
694 return nsGkAtoms::background_image
;
695 case eCSSEditableProperty_border
:
696 return nsGkAtoms::border
;
697 case eCSSEditableProperty_caption_side
:
698 return nsGkAtoms::caption_side
;
699 case eCSSEditableProperty_color
:
700 return nsGkAtoms::color
;
701 case eCSSEditableProperty_float
:
702 return nsGkAtoms::_float
;
703 case eCSSEditableProperty_font_family
:
704 return nsGkAtoms::font_family
;
705 case eCSSEditableProperty_font_size
:
706 return nsGkAtoms::font_size
;
707 case eCSSEditableProperty_font_style
:
708 return nsGkAtoms::font_style
;
709 case eCSSEditableProperty_font_weight
:
710 return nsGkAtoms::fontWeight
;
711 case eCSSEditableProperty_height
:
712 return nsGkAtoms::height
;
713 case eCSSEditableProperty_list_style_type
:
714 return nsGkAtoms::list_style_type
;
715 case eCSSEditableProperty_margin_left
:
716 return nsGkAtoms::marginLeft
;
717 case eCSSEditableProperty_margin_right
:
718 return nsGkAtoms::marginRight
;
719 case eCSSEditableProperty_text_align
:
720 return nsGkAtoms::textAlign
;
721 case eCSSEditableProperty_text_decoration
:
722 return nsGkAtoms::text_decoration
;
723 case eCSSEditableProperty_vertical_align
:
724 return nsGkAtoms::vertical_align
;
725 case eCSSEditableProperty_whitespace
:
726 return nsGkAtoms::white_space
;
727 case eCSSEditableProperty_width
:
728 return nsGkAtoms::width
;
729 case eCSSEditableProperty_NONE
:
730 // intentionally empty
733 MOZ_ASSERT_UNREACHABLE("Got unknown property");
738 void CSSEditUtils::GetCSSDeclarations(
739 const CSSEquivTable
* aEquivTable
, const nsAString
* aValue
,
740 HandlingFor aHandlingFor
, nsTArray
<CSSDeclaration
>& aOutCSSDeclarations
) {
742 aOutCSSDeclarations
.Clear();
744 // if we have an input value, let's use it
745 nsAutoString value
, lowerCasedValue
;
747 value
.Assign(*aValue
);
748 lowerCasedValue
.Assign(*aValue
);
749 ToLowerCase(lowerCasedValue
);
752 for (size_t index
= 0;; index
++) {
753 const nsCSSEditableProperty cssProperty
= aEquivTable
[index
].cssProperty
;
757 if (aHandlingFor
== HandlingFor::SettingStyle
||
758 aEquivTable
[index
].gettable
) {
759 nsAutoString cssValue
, cssPropertyString
;
760 // find the equivalent css value for the index-th property in
761 // the equivalence table
762 (*aEquivTable
[index
].processValueFunctor
)(
763 (aHandlingFor
== HandlingFor::SettingStyle
||
764 aEquivTable
[index
].caseSensitiveValue
)
767 cssValue
, aEquivTable
[index
].defaultValue
,
768 aEquivTable
[index
].prependValue
, aEquivTable
[index
].appendValue
);
769 nsStaticAtom
* const propertyAtom
= GetCSSPropertyAtom(cssProperty
);
770 if (MOZ_LIKELY(propertyAtom
)) {
771 aOutCSSDeclarations
.AppendElement(
772 CSSDeclaration
{*propertyAtom
, cssValue
});
779 void CSSEditUtils::GetCSSDeclarations(
780 Element
& aElement
, const EditorElementStyle
& aStyle
,
781 const nsAString
* aValue
, HandlingFor aHandlingFor
,
782 nsTArray
<CSSDeclaration
>& aOutCSSDeclarations
) {
783 nsStaticAtom
* const htmlProperty
=
784 aStyle
.IsInlineStyle() ? aStyle
.AsInlineStyle().mHTMLProperty
: nullptr;
785 const RefPtr
<nsAtom
> attributeOrStyle
=
786 aStyle
.IsInlineStyle() ? aStyle
.AsInlineStyle().mAttribute
789 const auto* equivTable
= [&]() -> const CSSEditUtils::CSSEquivTable
* {
790 if (nsGkAtoms::b
== htmlProperty
) {
791 return boldEquivTable
;
793 if (nsGkAtoms::i
== htmlProperty
) {
794 return italicEquivTable
;
796 if (nsGkAtoms::u
== htmlProperty
) {
797 return underlineEquivTable
;
799 if (nsGkAtoms::strike
== htmlProperty
) {
800 return strikeEquivTable
;
802 if (nsGkAtoms::tt
== htmlProperty
) {
805 if (!attributeOrStyle
) {
808 if (nsGkAtoms::font
== htmlProperty
) {
809 if (attributeOrStyle
== nsGkAtoms::color
) {
810 return fontColorEquivTable
;
812 if (attributeOrStyle
== nsGkAtoms::face
) {
813 return fontFaceEquivTable
;
815 if (attributeOrStyle
== nsGkAtoms::size
) {
816 return fontSizeEquivTable
;
818 MOZ_ASSERT(attributeOrStyle
== nsGkAtoms::bgcolor
);
820 if (attributeOrStyle
== nsGkAtoms::bgcolor
) {
821 return bgcolorEquivTable
;
823 if (attributeOrStyle
== nsGkAtoms::background
) {
824 return backgroundImageEquivTable
;
826 if (attributeOrStyle
== nsGkAtoms::text
) {
827 return textColorEquivTable
;
829 if (attributeOrStyle
== nsGkAtoms::border
) {
830 return borderEquivTable
;
832 if (attributeOrStyle
== nsGkAtoms::align
) {
833 if (aElement
.IsHTMLElement(nsGkAtoms::table
)) {
834 return tableAlignEquivTable
;
836 if (aElement
.IsHTMLElement(nsGkAtoms::hr
)) {
837 return hrAlignEquivTable
;
839 if (aElement
.IsAnyOfHTMLElements(nsGkAtoms::legend
, nsGkAtoms::caption
)) {
840 return captionAlignEquivTable
;
842 return textAlignEquivTable
;
844 if (attributeOrStyle
== nsGkAtoms::valign
) {
845 return verticalAlignEquivTable
;
847 if (attributeOrStyle
== nsGkAtoms::nowrap
) {
848 return nowrapEquivTable
;
850 if (attributeOrStyle
== nsGkAtoms::width
) {
851 return widthEquivTable
;
853 if (attributeOrStyle
== nsGkAtoms::height
||
854 (aElement
.IsHTMLElement(nsGkAtoms::hr
) &&
855 attributeOrStyle
== nsGkAtoms::size
)) {
856 return heightEquivTable
;
858 if (attributeOrStyle
== nsGkAtoms::type
&&
859 aElement
.IsAnyOfHTMLElements(nsGkAtoms::ol
, nsGkAtoms::ul
,
861 return listStyleTypeEquivTable
;
866 GetCSSDeclarations(equivTable
, aValue
, aHandlingFor
, aOutCSSDeclarations
);
870 // Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/
871 // aValue for the node, and return in aCount the number of CSS properties set
872 // by the call. The Element version returns aCount instead.
873 Result
<size_t, nsresult
> CSSEditUtils::SetCSSEquivalentToStyle(
874 WithTransaction aWithTransaction
, HTMLEditor
& aHTMLEditor
,
875 nsStyledElement
& aStyledElement
, const EditorElementStyle
& aStyleToSet
,
876 const nsAString
* aValue
) {
877 MOZ_DIAGNOSTIC_ASSERT(aStyleToSet
.IsCSSSettable(aStyledElement
));
879 // we can apply the styles only if the node is an element and if we have
880 // an equivalence for the requested HTML style in this implementation
882 // Find the CSS equivalence to the HTML style
883 AutoTArray
<CSSDeclaration
, 4> cssDeclarations
;
884 GetCSSDeclarations(aStyledElement
, aStyleToSet
, aValue
,
885 HandlingFor::SettingStyle
, cssDeclarations
);
887 // set the individual CSS inline styles
888 for (const CSSDeclaration
& cssDeclaration
: cssDeclarations
) {
889 nsresult rv
= SetCSSPropertyInternal(
890 aHTMLEditor
, aStyledElement
, MOZ_KnownLive(cssDeclaration
.mProperty
),
891 cssDeclaration
.mValue
, aWithTransaction
== WithTransaction::No
);
893 NS_WARNING("CSSEditUtils::SetCSSPropertyInternal() failed");
897 return cssDeclarations
.Length();
901 nsresult
CSSEditUtils::RemoveCSSEquivalentToStyle(
902 WithTransaction aWithTransaction
, HTMLEditor
& aHTMLEditor
,
903 nsStyledElement
& aStyledElement
, const EditorElementStyle
& aStyleToRemove
,
904 const nsAString
* aValue
) {
905 MOZ_DIAGNOSTIC_ASSERT(aStyleToRemove
.IsCSSRemovable(aStyledElement
));
907 // we can apply the styles only if the node is an element and if we have
908 // an equivalence for the requested HTML style in this implementation
910 // Find the CSS equivalence to the HTML style
911 AutoTArray
<CSSDeclaration
, 4> cssDeclarations
;
912 GetCSSDeclarations(aStyledElement
, aStyleToRemove
, aValue
,
913 HandlingFor::RemovingStyle
, cssDeclarations
);
915 // remove the individual CSS inline styles
916 for (const CSSDeclaration
& cssDeclaration
: cssDeclarations
) {
917 nsresult rv
= RemoveCSSPropertyInternal(
918 aHTMLEditor
, aStyledElement
, MOZ_KnownLive(cssDeclaration
.mProperty
),
919 cssDeclaration
.mValue
, aWithTransaction
== WithTransaction::No
);
921 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithoutTransaction() failed");
929 nsresult
CSSEditUtils::GetComputedCSSEquivalentTo(
930 Element
& aElement
, const EditorElementStyle
& aStyle
, nsAString
& aOutValue
) {
931 return GetCSSEquivalentTo(aElement
, aStyle
, aOutValue
, StyleType::Computed
);
935 nsresult
CSSEditUtils::GetCSSEquivalentTo(Element
& aElement
,
936 const EditorElementStyle
& aStyle
,
937 nsAString
& aOutValue
,
938 StyleType aStyleType
) {
939 MOZ_ASSERT_IF(aStyle
.IsInlineStyle(),
940 !aStyle
.AsInlineStyle().IsStyleToClearAllInlineStyles());
941 MOZ_DIAGNOSTIC_ASSERT(aStyle
.IsCSSSettable(aElement
) ||
942 aStyle
.IsCSSRemovable(aElement
));
944 aOutValue
.Truncate();
945 AutoTArray
<CSSDeclaration
, 4> cssDeclarations
;
946 GetCSSDeclarations(aElement
, aStyle
, nullptr, HandlingFor::GettingStyle
,
948 nsAutoString valueString
;
949 for (const CSSDeclaration
& cssDeclaration
: cssDeclarations
) {
950 valueString
.Truncate();
951 // retrieve the specified/computed value of the property
952 if (aStyleType
== StyleType::Computed
) {
953 nsresult rv
= GetComputedCSSInlinePropertyBase(
954 aElement
, MOZ_KnownLive(cssDeclaration
.mProperty
), valueString
);
956 NS_WARNING("CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
960 nsresult rv
= GetSpecifiedCSSInlinePropertyBase(
961 aElement
, cssDeclaration
.mProperty
, valueString
);
963 NS_WARNING("CSSEditUtils::GetSpecifiedCSSInlinePropertyBase() failed");
967 // append the value to aOutValue (possibly with a leading white-space)
968 if (!aOutValue
.IsEmpty()) {
969 aOutValue
.Append(HTMLEditUtils::kSpace
);
971 aOutValue
.Append(valueString
);
976 // Does the node aContent (or its parent, if it's not an element node) have a
977 // CSS style equivalent to the HTML style
978 // aHTMLProperty/aAttribute/valueString? The value of aStyleType controls
979 // the styles we retrieve: specified or computed. The return value aIsSet is
980 // true if the CSS styles are set.
982 // The nsIContent variant returns aIsSet instead of using an out parameter, and
983 // does not modify aValue.
986 Result
<bool, nsresult
> CSSEditUtils::IsComputedCSSEquivalentTo(
987 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
,
988 const EditorInlineStyle
& aStyle
, nsAString
& aInOutValue
) {
989 return IsCSSEquivalentTo(aHTMLEditor
, aContent
, aStyle
, aInOutValue
,
990 StyleType::Computed
);
994 Result
<bool, nsresult
> CSSEditUtils::IsSpecifiedCSSEquivalentTo(
995 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
,
996 const EditorInlineStyle
& aStyle
, nsAString
& aInOutValue
) {
997 return IsCSSEquivalentTo(aHTMLEditor
, aContent
, aStyle
, aInOutValue
,
998 StyleType::Specified
);
1002 Result
<bool, nsresult
> CSSEditUtils::IsCSSEquivalentTo(
1003 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
,
1004 const EditorInlineStyle
& aStyle
, nsAString
& aInOutValue
,
1005 StyleType aStyleType
) {
1006 MOZ_ASSERT(!aStyle
.IsStyleToClearAllInlineStyles());
1008 nsAutoString
htmlValueString(aInOutValue
);
1010 // FYI: Cannot use InclusiveAncestorsOfType here because
1011 // GetCSSEquivalentTo() may flush pending notifications.
1012 for (RefPtr
<Element
> element
= aContent
.GetAsElementOrParentElement();
1013 element
; element
= element
->GetParentElement()) {
1014 nsCOMPtr
<nsINode
> parentNode
= element
->GetParentNode();
1015 aInOutValue
.Assign(htmlValueString
);
1016 // get the value of the CSS equivalent styles
1017 nsresult rv
= GetCSSEquivalentTo(*element
, aStyle
, aInOutValue
, aStyleType
);
1018 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
1019 return Err(NS_ERROR_EDITOR_DESTROYED
);
1021 if (NS_FAILED(rv
)) {
1023 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
1027 if (NS_WARN_IF(parentNode
!= element
->GetParentNode())) {
1028 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1031 // early way out if we can
1032 if (aInOutValue
.IsEmpty()) {
1036 if (nsGkAtoms::b
== aStyle
.mHTMLProperty
) {
1037 if (aInOutValue
.EqualsLiteral("bold")) {
1039 } else if (aInOutValue
.EqualsLiteral("normal")) {
1041 } else if (aInOutValue
.EqualsLiteral("bolder")) {
1043 aInOutValue
.AssignLiteral("bold");
1047 nsAutoString
value(aInOutValue
);
1048 weight
= value
.ToInteger(&rvIgnored
);
1049 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored
),
1050 "nsAString::ToInteger() failed, but ignored");
1053 aInOutValue
.AssignLiteral(u
"bold");
1056 aInOutValue
.AssignLiteral(u
"normal");
1059 } else if (nsGkAtoms::i
== aStyle
.mHTMLProperty
) {
1060 if (aInOutValue
.EqualsLiteral(u
"italic") ||
1061 aInOutValue
.EqualsLiteral(u
"oblique")) {
1064 } else if (nsGkAtoms::u
== aStyle
.mHTMLProperty
) {
1065 isSet
= ChangeStyleTransaction::ValueIncludes(
1066 NS_ConvertUTF16toUTF8(aInOutValue
), "underline"_ns
);
1067 } else if (nsGkAtoms::strike
== aStyle
.mHTMLProperty
) {
1068 isSet
= ChangeStyleTransaction::ValueIncludes(
1069 NS_ConvertUTF16toUTF8(aInOutValue
), "line-through"_ns
);
1070 } else if ((nsGkAtoms::font
== aStyle
.mHTMLProperty
&&
1071 aStyle
.mAttribute
== nsGkAtoms::color
) ||
1072 aStyle
.mAttribute
== nsGkAtoms::bgcolor
) {
1073 isSet
= htmlValueString
.IsEmpty() ||
1074 HTMLEditUtils::IsSameCSSColorValue(htmlValueString
, aInOutValue
);
1075 } else if (nsGkAtoms::tt
== aStyle
.mHTMLProperty
) {
1076 isSet
= StringBeginsWith(aInOutValue
, u
"monospace"_ns
);
1077 } else if (nsGkAtoms::font
== aStyle
.mHTMLProperty
&&
1078 aStyle
.mAttribute
== nsGkAtoms::face
) {
1079 if (!htmlValueString
.IsEmpty()) {
1080 const char16_t commaSpace
[] = {char16_t(','), HTMLEditUtils::kSpace
, 0};
1081 const char16_t comma
[] = {char16_t(','), 0};
1082 htmlValueString
.ReplaceSubstring(commaSpace
, comma
);
1083 nsAutoString
valueStringNorm(aInOutValue
);
1084 valueStringNorm
.ReplaceSubstring(commaSpace
, comma
);
1085 isSet
= htmlValueString
.Equals(valueStringNorm
,
1086 nsCaseInsensitiveStringComparator
);
1091 } else if (aStyle
.IsStyleOfFontSize()) {
1092 if (htmlValueString
.IsEmpty()) {
1095 switch (nsContentUtils::ParseLegacyFontSize(htmlValueString
)) {
1097 return aInOutValue
.EqualsLiteral("x-small");
1099 return aInOutValue
.EqualsLiteral("small");
1101 return aInOutValue
.EqualsLiteral("medium");
1103 return aInOutValue
.EqualsLiteral("large");
1105 return aInOutValue
.EqualsLiteral("x-large");
1107 return aInOutValue
.EqualsLiteral("xx-large");
1109 return aInOutValue
.EqualsLiteral("xxx-large");
1112 } else if (aStyle
.mAttribute
== nsGkAtoms::align
) {
1118 if (!htmlValueString
.IsEmpty() &&
1119 htmlValueString
.Equals(aInOutValue
,
1120 nsCaseInsensitiveStringComparator
)) {
1124 if (htmlValueString
.EqualsLiteral(u
"-moz-editor-invert-value")) {
1132 if (!aStyle
.IsStyleOfTextDecoration(
1133 EditorInlineStyle::IgnoreSElement::Yes
)) {
1137 // Unfortunately, the value of the text-decoration property is not
1138 // inherited. that means that we have to look at ancestors of node to see
1139 // if they are underlined.
1145 Result
<bool, nsresult
> CSSEditUtils::HaveComputedCSSEquivalentStyles(
1146 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
,
1147 const EditorInlineStyle
& aStyle
) {
1148 return HaveCSSEquivalentStyles(aHTMLEditor
, aContent
, aStyle
,
1149 StyleType::Computed
);
1153 Result
<bool, nsresult
> CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(
1154 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
,
1155 const EditorInlineStyle
& aStyle
) {
1156 return HaveCSSEquivalentStyles(aHTMLEditor
, aContent
, aStyle
,
1157 StyleType::Specified
);
1161 Result
<bool, nsresult
> CSSEditUtils::HaveCSSEquivalentStyles(
1162 const HTMLEditor
& aHTMLEditor
, nsIContent
& aContent
,
1163 const EditorInlineStyle
& aStyle
, StyleType aStyleType
) {
1164 MOZ_ASSERT(!aStyle
.IsStyleToClearAllInlineStyles());
1166 // FYI: Unfortunately, we cannot use InclusiveAncestorsOfType here
1167 // because GetCSSEquivalentTo() may flush pending notifications.
1168 nsAutoString valueString
;
1169 for (RefPtr
<Element
> element
= aContent
.GetAsElementOrParentElement();
1170 element
; element
= element
->GetParentElement()) {
1171 nsCOMPtr
<nsINode
> parentNode
= element
->GetParentNode();
1172 // get the value of the CSS equivalent styles
1173 nsresult rv
= GetCSSEquivalentTo(*element
, aStyle
, valueString
, aStyleType
);
1174 if (NS_WARN_IF(aHTMLEditor
.Destroyed())) {
1175 return Err(NS_ERROR_EDITOR_DESTROYED
);
1177 if (NS_FAILED(rv
)) {
1179 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
1183 if (NS_WARN_IF(parentNode
!= element
->GetParentNode())) {
1184 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE
);
1187 if (!valueString
.IsEmpty()) {
1191 if (!aStyle
.IsStyleOfTextDecoration(
1192 EditorInlineStyle::IgnoreSElement::Yes
)) {
1196 // Unfortunately, the value of the text-decoration property is not
1198 // that means that we have to look at ancestors of node to see if they
1205 // ElementsSameStyle compares two elements and checks if they have the same
1206 // specified CSS declarations in the STYLE attribute
1207 // The answer is always negative if at least one of them carries an ID or a
1211 bool CSSEditUtils::DoStyledElementsHaveSameStyle(
1212 nsStyledElement
& aStyledElement
, nsStyledElement
& aOtherStyledElement
) {
1213 if (aStyledElement
.HasAttr(nsGkAtoms::id
) ||
1214 aOtherStyledElement
.HasAttr(nsGkAtoms::id
)) {
1215 // at least one of the spans carries an ID ; suspect a CSS rule applies to
1216 // it and refuse to merge the nodes
1220 nsAutoString firstClass
, otherClass
;
1221 bool isElementClassSet
=
1222 aStyledElement
.GetAttr(nsGkAtoms::_class
, firstClass
);
1223 bool isOtherElementClassSet
= aOtherStyledElement
.GetAttr(
1224 kNameSpaceID_None
, nsGkAtoms::_class
, otherClass
);
1225 if (isElementClassSet
&& isOtherElementClassSet
) {
1226 // both spans carry a class, let's compare them
1227 if (!firstClass
.Equals(otherClass
)) {
1228 // WARNING : technically, the comparison just above is questionable :
1229 // from a pure HTML/CSS point of view class="a b" is NOT the same than
1230 // class="b a" because a CSS rule could test the exact value of the class
1231 // attribute to be "a b" for instance ; from a user's point of view, a
1232 // wysiwyg editor should probably NOT make any difference. CSS people
1233 // need to discuss this issue before any modification.
1236 } else if (isElementClassSet
|| isOtherElementClassSet
) {
1237 // one span only carries a class, early way out
1241 // XXX If `GetPropertyValue()` won't run script, we can stop using
1243 nsCOMPtr
<nsICSSDeclaration
> firstCSSDecl
= aStyledElement
.Style();
1244 if (!firstCSSDecl
) {
1245 NS_WARNING("nsStyledElement::Style() failed");
1248 nsCOMPtr
<nsICSSDeclaration
> otherCSSDecl
= aOtherStyledElement
.Style();
1249 if (!otherCSSDecl
) {
1250 NS_WARNING("nsStyledElement::Style() failed");
1254 const uint32_t firstLength
= firstCSSDecl
->Length();
1255 const uint32_t otherLength
= otherCSSDecl
->Length();
1256 if (firstLength
!= otherLength
) {
1257 // early way out if we can
1262 // no inline style !
1266 for (uint32_t i
= 0; i
< firstLength
; i
++) {
1267 nsAutoCString firstValue
, otherValue
;
1268 nsAutoCString propertyNameString
;
1269 firstCSSDecl
->Item(i
, propertyNameString
);
1270 firstCSSDecl
->GetPropertyValue(propertyNameString
, firstValue
);
1271 otherCSSDecl
->GetPropertyValue(propertyNameString
, otherValue
);
1272 // FIXME: We need to handle all properties whose values are color.
1273 // However, it's too expensive if we keep using string property names.
1274 if (propertyNameString
.EqualsLiteral("color") ||
1275 propertyNameString
.EqualsLiteral("background-color")) {
1276 if (!HTMLEditUtils::IsSameCSSColorValue(firstValue
, otherValue
)) {
1279 } else if (!firstValue
.Equals(otherValue
)) {
1283 for (uint32_t i
= 0; i
< otherLength
; i
++) {
1284 nsAutoCString firstValue
, otherValue
;
1285 nsAutoCString propertyNameString
;
1286 otherCSSDecl
->Item(i
, propertyNameString
);
1287 otherCSSDecl
->GetPropertyValue(propertyNameString
, otherValue
);
1288 firstCSSDecl
->GetPropertyValue(propertyNameString
, firstValue
);
1289 // FIXME: We need to handle all properties whose values are color.
1290 // However, it's too expensive if we keep using string property names.
1291 if (propertyNameString
.EqualsLiteral("color") ||
1292 propertyNameString
.EqualsLiteral("background-color")) {
1293 if (!HTMLEditUtils::IsSameCSSColorValue(firstValue
, otherValue
)) {
1296 } else if (!firstValue
.Equals(otherValue
)) {
1304 } // namespace mozilla