Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / editor / libeditor / CSSEditUtils.cpp
blob39ae58eaa7a7a8983c3385ba6c3f0eacce60e4ce
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"
22 #include "nsCOMPtr.h"
23 #include "nsCSSProps.h"
24 #include "nsColor.h"
25 #include "nsComputedDOMStyle.h"
26 #include "nsDebug.h"
27 #include "nsDependentSubstring.h"
28 #include "nsError.h"
29 #include "nsGkAtoms.h"
30 #include "nsAtom.h"
31 #include "nsIContent.h"
32 #include "nsICSSDeclaration.h"
33 #include "nsINode.h"
34 #include "nsISupportsImpl.h"
35 #include "nsISupportsUtils.h"
36 #include "nsLiteralString.h"
37 #include "nsPIDOMWindow.h"
38 #include "nsReadableUtils.h"
39 #include "nsString.h"
40 #include "nsStringFwd.h"
41 #include "nsStringIterator.h"
42 #include "nsStyledElement.h"
43 #include "nsUnicharUtils.h"
45 namespace mozilla {
47 using namespace dom;
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");
56 } else {
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) {
74 if (aInputString) {
75 aOutputString.Assign(*aInputString);
76 } else
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();
86 if (aInputString) {
87 if (aPrependString) {
88 AppendASCIItoUTF16(MakeStringSpan(aPrependString), aOutputString);
90 aOutputString.Append(*aInputString);
91 if (aAppendString) {
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();
103 if (aInputString) {
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();
117 if (aInputString) {
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();
142 if (aInputString) {
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");
149 } else {
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();
161 if (aInputString) {
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");
168 } else {
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
289 // static
290 bool CSSEditUtils::IsCSSEditableStyle(const Element& aElement,
291 const EditorElementStyle& aStyle) {
292 return CSSEditUtils::IsCSSEditableStyle(*aElement.NodeInfo()->NameAtom(),
293 aStyle);
296 // static
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()
303 : aStyle.Style();
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))) {
313 return true;
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)) {
330 return true;
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)) {
338 return true;
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)) {
346 return true;
349 // attribute BGCOLOR on other elements
350 if (attributeOrStyle == nsGkAtoms::bgcolor) {
351 return true;
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)) {
359 return true;
362 // attributes HEIGHT and WIDTH on <table>
363 if (&aTagName == nsGkAtoms::table && (attributeOrStyle == nsGkAtoms::height ||
364 attributeOrStyle == nsGkAtoms::width)) {
365 return true;
368 // attributes SIZE and WIDTH on <hr>
369 if (&aTagName == nsGkAtoms::hr && (attributeOrStyle == nsGkAtoms::size ||
370 attributeOrStyle == nsGkAtoms::width)) {
371 return true;
374 // attribute TYPE on <ol>, <ul> and <li>
375 if (attributeOrStyle == nsGkAtoms::type &&
376 (&aTagName == nsGkAtoms::ol || &aTagName == nsGkAtoms::ul ||
377 &aTagName == nsGkAtoms::li)) {
378 return true;
381 if (&aTagName == nsGkAtoms::img && (attributeOrStyle == nsGkAtoms::border ||
382 attributeOrStyle == nsGkAtoms::width ||
383 attributeOrStyle == nsGkAtoms::height)) {
384 return true;
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)) {
394 return true;
397 return false;
400 // The lowest level above the transaction; adds the CSS declaration
401 // "aProperty : aValue" to the inline styles carried by aStyledElement
403 // static
404 nsresult CSSEditUtils::SetCSSPropertyInternal(HTMLEditor& aHTMLEditor,
405 nsStyledElement& aStyledElement,
406 nsAtom& aProperty,
407 const nsAString& aValue,
408 bool aSuppressTxn) {
409 RefPtr<ChangeStyleTransaction> transaction =
410 ChangeStyleTransaction::Create(aStyledElement, aProperty, aValue);
411 if (aSuppressTxn) {
412 nsresult rv = transaction->DoTransaction();
413 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
414 "ChangeStyleTransaction::DoTransaction() failed");
415 return rv;
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");
423 return rv;
426 // static
427 nsresult CSSEditUtils::SetCSSPropertyPixelsWithTransaction(
428 HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty,
429 int32_t aIntValue) {
430 nsAutoString s;
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");
436 return rv;
439 // static
440 nsresult CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(
441 nsStyledElement& aStyledElement, const nsAtom& aProperty,
442 int32_t aIntValue) {
443 nsCOMPtr<nsICSSDeclaration> cssDecl = aStyledElement.Style();
445 nsAutoCString propertyNameString;
446 aProperty.ToUTF8String(propertyNameString);
448 nsAutoCString s;
449 s.AppendInt(aIntValue);
450 s.AppendLiteral("px");
452 ErrorResult error;
453 cssDecl->SetProperty(propertyNameString, s, EmptyCString(), error);
454 if (error.Failed()) {
455 NS_WARNING("nsICSSDeclaration::SetProperty() failed");
456 return error.StealNSResult();
459 return NS_OK;
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
466 // static
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);
472 if (aSuppressTxn) {
473 nsresult rv = transaction->DoTransaction();
474 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
475 "ChangeStyleTransaction::DoTransaction() failed");
476 return rv;
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");
484 return rv;
487 // static
488 nsresult CSSEditUtils::GetSpecifiedProperty(nsIContent& aContent,
489 nsAtom& aCSSProperty,
490 nsAString& aValue) {
491 nsresult rv =
492 GetSpecifiedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
493 NS_WARNING_ASSERTION(
494 NS_SUCCEEDED(rv),
495 "CSSEditUtils::GeSpecifiedCSSInlinePropertyBase() failed");
496 return rv;
499 // static
500 nsresult CSSEditUtils::GetComputedProperty(nsIContent& aContent,
501 nsAtom& aCSSProperty,
502 nsAString& aValue) {
503 nsresult rv =
504 GetComputedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
505 NS_WARNING_ASSERTION(
506 NS_SUCCEEDED(rv),
507 "CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
508 return rv;
511 // static
512 nsresult CSSEditUtils::GetComputedCSSInlinePropertyBase(nsIContent& aContent,
513 nsAtom& aCSSProperty,
514 nsAString& aValue) {
515 aValue.Truncate();
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
531 // property id.
533 // FIXME: Maybe we can avoid copying aValue too, though it's no worse than
534 // what we used to do.
535 nsAutoCString value;
536 computedDOMStyle->GetPropertyValue(nsAtomCString(&aCSSProperty), value);
537 CopyUTF8toUTF16(value, aValue);
538 return NS_OK;
541 // static
542 nsresult CSSEditUtils::GetSpecifiedCSSInlinePropertyBase(nsIContent& aContent,
543 nsAtom& aCSSProperty,
544 nsAString& aValue) {
545 aValue.Truncate();
547 RefPtr<Element> element = aContent.GetAsElementOrParentElement();
548 if (NS_WARN_IF(!element)) {
549 return NS_ERROR_INVALID_ARG;
552 RefPtr<DeclarationBlock> decl = element->GetInlineStyleDeclaration();
553 if (!decl) {
554 return NS_OK;
557 // FIXME: Same comments as above.
558 nsCSSPropertyID prop =
559 nsCSSProps::LookupProperty(nsAtomCString(&aCSSProperty));
560 MOZ_ASSERT(prop != eCSSProperty_UNKNOWN);
562 nsAutoCString value;
563 decl->GetPropertyValueByID(prop, value);
564 CopyUTF8toUTF16(value, aValue);
565 return NS_OK;
568 // static
569 already_AddRefed<nsComputedDOMStyle> CSSEditUtils::GetComputedStyle(
570 Element* aElement) {
571 MOZ_ASSERT(aElement);
573 Document* document = aElement->GetComposedDoc();
574 if (NS_WARN_IF(!document)) {
575 return nullptr;
578 RefPtr<nsComputedDOMStyle> computedDOMStyle = NS_NewComputedDOMStyle(
579 aElement, u""_ns, document, nsComputedDOMStyle::StyleType::All,
580 IgnoreErrors());
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
587 // static
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);
595 if (NS_FAILED(rv)) {
596 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
597 return Err(rv);
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
615 // static
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?
620 if (NS_FAILED(rv)) {
621 NS_WARNING("failed to get editor.background_color");
622 aColor.AssignLiteral("#ffffff"); // Default to white
624 return;
627 if (Preferences::GetBool("browser.display.use_system_colors", false)) {
628 return;
631 nsresult rv =
632 Preferences::GetString("browser.display.background_color", aColor);
633 // XXX Why don't you validate the pref value?
634 if (NS_FAILED(rv)) {
635 NS_WARNING("failed to get browser.display.background_color");
636 aColor.AssignLiteral("#ffffff"); // Default to white
640 // static
641 void CSSEditUtils::ParseLength(const nsAString& aString, float* aValue,
642 nsAtom** aUnit) {
643 if (aString.IsEmpty()) {
644 *aValue = 0;
645 *aUnit = NS_Atomize(aString).take();
646 return;
649 nsAString::const_iterator iter;
650 aString.BeginReading(iter);
652 float a = 10.0f, b = 1.0f, value = 0;
653 int8_t sign = 1;
654 int32_t i = 0, j = aString.Length();
655 char16_t c;
656 bool floatingPointFound = false;
657 c = *iter;
658 if (char16_t('-') == c) {
659 sign = -1;
660 iter++;
661 i++;
662 } else if (char16_t('+') == c) {
663 iter++;
664 i++;
666 while (i < j) {
667 c = *iter;
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')));
673 b = b / 10 * a;
674 } else if (!floatingPointFound && (char16_t('.') == c)) {
675 floatingPointFound = true;
676 a = 1.0f;
677 b = 0.1f;
678 } else
679 break;
680 iter++;
681 i++;
683 *aValue = value * sign;
684 *aUnit = NS_Atomize(StringTail(aString, j - i)).take();
687 // static
688 nsStaticAtom* CSSEditUtils::GetCSSPropertyAtom(
689 nsCSSEditableProperty aProperty) {
690 switch (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
731 return nullptr;
733 MOZ_ASSERT_UNREACHABLE("Got unknown property");
734 return nullptr;
737 // static
738 void CSSEditUtils::GetCSSDeclarations(
739 const CSSEquivTable* aEquivTable, const nsAString* aValue,
740 HandlingFor aHandlingFor, nsTArray<CSSDeclaration>& aOutCSSDeclarations) {
741 // clear arrays
742 aOutCSSDeclarations.Clear();
744 // if we have an input value, let's use it
745 nsAutoString value, lowerCasedValue;
746 if (aValue) {
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;
754 if (!cssProperty) {
755 break;
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)
765 ? &value
766 : &lowerCasedValue,
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});
778 // static
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
787 : aStyle.Style();
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) {
803 return ttEquivTable;
805 if (!attributeOrStyle) {
806 return nullptr;
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,
860 nsGkAtoms::li)) {
861 return listStyleTypeEquivTable;
863 return nullptr;
864 }();
865 if (equivTable) {
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);
892 if (NS_FAILED(rv)) {
893 NS_WARNING("CSSEditUtils::SetCSSPropertyInternal() failed");
894 return Err(rv);
897 return cssDeclarations.Length();
900 // static
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);
920 if (NS_FAILED(rv)) {
921 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithoutTransaction() failed");
922 return rv;
925 return NS_OK;
928 // static
929 nsresult CSSEditUtils::GetComputedCSSEquivalentTo(
930 Element& aElement, const EditorElementStyle& aStyle, nsAString& aOutValue) {
931 return GetCSSEquivalentTo(aElement, aStyle, aOutValue, StyleType::Computed);
934 // static
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,
947 cssDeclarations);
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);
955 if (NS_FAILED(rv)) {
956 NS_WARNING("CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
957 return rv;
959 } else {
960 nsresult rv = GetSpecifiedCSSInlinePropertyBase(
961 aElement, cssDeclaration.mProperty, valueString);
962 if (NS_FAILED(rv)) {
963 NS_WARNING("CSSEditUtils::GetSpecifiedCSSInlinePropertyBase() failed");
964 return rv;
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);
973 return NS_OK;
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.
985 // static
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);
993 // static
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);
1001 // static
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);
1009 bool isSet = false;
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)) {
1022 NS_WARNING(
1023 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
1024 "failed");
1025 return Err(rv);
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()) {
1033 return isSet;
1036 if (nsGkAtoms::b == aStyle.mHTMLProperty) {
1037 if (aInOutValue.EqualsLiteral("bold")) {
1038 isSet = true;
1039 } else if (aInOutValue.EqualsLiteral("normal")) {
1040 isSet = false;
1041 } else if (aInOutValue.EqualsLiteral("bolder")) {
1042 isSet = true;
1043 aInOutValue.AssignLiteral("bold");
1044 } else {
1045 int32_t weight = 0;
1046 nsresult rvIgnored;
1047 nsAutoString value(aInOutValue);
1048 weight = value.ToInteger(&rvIgnored);
1049 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1050 "nsAString::ToInteger() failed, but ignored");
1051 if (400 < weight) {
1052 isSet = true;
1053 aInOutValue.AssignLiteral(u"bold");
1054 } else {
1055 isSet = false;
1056 aInOutValue.AssignLiteral(u"normal");
1059 } else if (nsGkAtoms::i == aStyle.mHTMLProperty) {
1060 if (aInOutValue.EqualsLiteral(u"italic") ||
1061 aInOutValue.EqualsLiteral(u"oblique")) {
1062 isSet = true;
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);
1087 } else {
1088 isSet = true;
1090 return isSet;
1091 } else if (aStyle.IsStyleOfFontSize()) {
1092 if (htmlValueString.IsEmpty()) {
1093 return true;
1095 switch (nsContentUtils::ParseLegacyFontSize(htmlValueString)) {
1096 case 1:
1097 return aInOutValue.EqualsLiteral("x-small");
1098 case 2:
1099 return aInOutValue.EqualsLiteral("small");
1100 case 3:
1101 return aInOutValue.EqualsLiteral("medium");
1102 case 4:
1103 return aInOutValue.EqualsLiteral("large");
1104 case 5:
1105 return aInOutValue.EqualsLiteral("x-large");
1106 case 6:
1107 return aInOutValue.EqualsLiteral("xx-large");
1108 case 7:
1109 return aInOutValue.EqualsLiteral("xxx-large");
1111 return false;
1112 } else if (aStyle.mAttribute == nsGkAtoms::align) {
1113 isSet = true;
1114 } else {
1115 return false;
1118 if (!htmlValueString.IsEmpty() &&
1119 htmlValueString.Equals(aInOutValue,
1120 nsCaseInsensitiveStringComparator)) {
1121 isSet = true;
1124 if (htmlValueString.EqualsLiteral(u"-moz-editor-invert-value")) {
1125 isSet = !isSet;
1128 if (isSet) {
1129 return true;
1132 if (!aStyle.IsStyleOfTextDecoration(
1133 EditorInlineStyle::IgnoreSElement::Yes)) {
1134 return isSet;
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.
1141 return isSet;
1144 // static
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);
1152 // static
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);
1160 // static
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)) {
1178 NS_WARNING(
1179 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
1180 "failed");
1181 return Err(rv);
1183 if (NS_WARN_IF(parentNode != element->GetParentNode())) {
1184 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1187 if (!valueString.IsEmpty()) {
1188 return true;
1191 if (!aStyle.IsStyleOfTextDecoration(
1192 EditorInlineStyle::IgnoreSElement::Yes)) {
1193 return false;
1196 // Unfortunately, the value of the text-decoration property is not
1197 // inherited.
1198 // that means that we have to look at ancestors of node to see if they
1199 // are underlined.
1202 return false;
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
1208 // class
1210 // static
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
1217 return false;
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.
1234 return false;
1236 } else if (isElementClassSet || isOtherElementClassSet) {
1237 // one span only carries a class, early way out
1238 return false;
1241 // XXX If `GetPropertyValue()` won't run script, we can stop using
1242 // nsCOMPtr here.
1243 nsCOMPtr<nsICSSDeclaration> firstCSSDecl = aStyledElement.Style();
1244 if (!firstCSSDecl) {
1245 NS_WARNING("nsStyledElement::Style() failed");
1246 return false;
1248 nsCOMPtr<nsICSSDeclaration> otherCSSDecl = aOtherStyledElement.Style();
1249 if (!otherCSSDecl) {
1250 NS_WARNING("nsStyledElement::Style() failed");
1251 return false;
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
1258 return false;
1261 if (!firstLength) {
1262 // no inline style !
1263 return true;
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)) {
1277 return false;
1279 } else if (!firstValue.Equals(otherValue)) {
1280 return false;
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)) {
1294 return false;
1296 } else if (!firstValue.Equals(otherValue)) {
1297 return false;
1301 return true;
1304 } // namespace mozilla