Bug 1772588 [wpt PR 34302] - [wpt] Add test for block-in-inline offsetParent., a...
[gecko.git] / editor / libeditor / CSSEditUtils.cpp
blobf85eff07c96be10994ea5113c485082c991411ca
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 "HTMLEditor.h"
10 #include "HTMLEditUtils.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/DeclarationBlock.h"
14 #include "mozilla/mozalloc.h"
15 #include "mozilla/Preferences.h"
16 #include "mozilla/StaticPrefs_editor.h"
17 #include "mozilla/dom/Document.h"
18 #include "mozilla/dom/Element.h"
19 #include "nsAString.h"
20 #include "nsCOMPtr.h"
21 #include "nsCSSProps.h"
22 #include "nsColor.h"
23 #include "nsComputedDOMStyle.h"
24 #include "nsDebug.h"
25 #include "nsDependentSubstring.h"
26 #include "nsError.h"
27 #include "nsGkAtoms.h"
28 #include "nsAtom.h"
29 #include "nsIContent.h"
30 #include "nsICSSDeclaration.h"
31 #include "nsINode.h"
32 #include "nsISupportsImpl.h"
33 #include "nsISupportsUtils.h"
34 #include "nsLiteralString.h"
35 #include "nsPIDOMWindow.h"
36 #include "nsReadableUtils.h"
37 #include "nsString.h"
38 #include "nsStringFwd.h"
39 #include "nsStringIterator.h"
40 #include "nsStyledElement.h"
41 #include "nsUnicharUtils.h"
43 namespace mozilla {
45 using namespace dom;
47 static void ProcessBValue(const nsAString* aInputString,
48 nsAString& aOutputString,
49 const char* aDefaultValueString,
50 const char* aPrependString,
51 const char* aAppendString) {
52 if (aInputString && aInputString->EqualsLiteral("-moz-editor-invert-value")) {
53 aOutputString.AssignLiteral("normal");
54 } else {
55 aOutputString.AssignLiteral("bold");
59 static void ProcessDefaultValue(const nsAString* aInputString,
60 nsAString& aOutputString,
61 const char* aDefaultValueString,
62 const char* aPrependString,
63 const char* aAppendString) {
64 CopyASCIItoUTF16(MakeStringSpan(aDefaultValueString), aOutputString);
67 static void ProcessSameValue(const nsAString* aInputString,
68 nsAString& aOutputString,
69 const char* aDefaultValueString,
70 const char* aPrependString,
71 const char* aAppendString) {
72 if (aInputString) {
73 aOutputString.Assign(*aInputString);
74 } else
75 aOutputString.Truncate();
78 static void ProcessExtendedValue(const nsAString* aInputString,
79 nsAString& aOutputString,
80 const char* aDefaultValueString,
81 const char* aPrependString,
82 const char* aAppendString) {
83 aOutputString.Truncate();
84 if (aInputString) {
85 if (aPrependString) {
86 AppendASCIItoUTF16(MakeStringSpan(aPrependString), aOutputString);
88 aOutputString.Append(*aInputString);
89 if (aAppendString) {
90 AppendASCIItoUTF16(MakeStringSpan(aAppendString), aOutputString);
95 static void ProcessLengthValue(const nsAString* aInputString,
96 nsAString& aOutputString,
97 const char* aDefaultValueString,
98 const char* aPrependString,
99 const char* aAppendString) {
100 aOutputString.Truncate();
101 if (aInputString) {
102 aOutputString.Append(*aInputString);
103 if (-1 == aOutputString.FindChar(char16_t('%'))) {
104 aOutputString.AppendLiteral("px");
109 static void ProcessListStyleTypeValue(const nsAString* aInputString,
110 nsAString& aOutputString,
111 const char* aDefaultValueString,
112 const char* aPrependString,
113 const char* aAppendString) {
114 aOutputString.Truncate();
115 if (aInputString) {
116 if (aInputString->EqualsLiteral("1")) {
117 aOutputString.AppendLiteral("decimal");
118 } else if (aInputString->EqualsLiteral("a")) {
119 aOutputString.AppendLiteral("lower-alpha");
120 } else if (aInputString->EqualsLiteral("A")) {
121 aOutputString.AppendLiteral("upper-alpha");
122 } else if (aInputString->EqualsLiteral("i")) {
123 aOutputString.AppendLiteral("lower-roman");
124 } else if (aInputString->EqualsLiteral("I")) {
125 aOutputString.AppendLiteral("upper-roman");
126 } else if (aInputString->EqualsLiteral("square") ||
127 aInputString->EqualsLiteral("circle") ||
128 aInputString->EqualsLiteral("disc")) {
129 aOutputString.Append(*aInputString);
134 static void ProcessMarginLeftValue(const nsAString* aInputString,
135 nsAString& aOutputString,
136 const char* aDefaultValueString,
137 const char* aPrependString,
138 const char* aAppendString) {
139 aOutputString.Truncate();
140 if (aInputString) {
141 if (aInputString->EqualsLiteral("center") ||
142 aInputString->EqualsLiteral("-moz-center")) {
143 aOutputString.AppendLiteral("auto");
144 } else if (aInputString->EqualsLiteral("right") ||
145 aInputString->EqualsLiteral("-moz-right")) {
146 aOutputString.AppendLiteral("auto");
147 } else {
148 aOutputString.AppendLiteral("0px");
153 static void ProcessMarginRightValue(const nsAString* aInputString,
154 nsAString& aOutputString,
155 const char* aDefaultValueString,
156 const char* aPrependString,
157 const char* aAppendString) {
158 aOutputString.Truncate();
159 if (aInputString) {
160 if (aInputString->EqualsLiteral("center") ||
161 aInputString->EqualsLiteral("-moz-center")) {
162 aOutputString.AppendLiteral("auto");
163 } else if (aInputString->EqualsLiteral("left") ||
164 aInputString->EqualsLiteral("-moz-left")) {
165 aOutputString.AppendLiteral("auto");
166 } else {
167 aOutputString.AppendLiteral("0px");
172 #define CSS_EQUIV_TABLE_NONE \
173 { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
175 const CSSEditUtils::CSSEquivTable boldEquivTable[] = {
176 {CSSEditUtils::eCSSEditableProperty_font_weight, true, false, ProcessBValue,
177 nullptr, nullptr, nullptr},
178 CSS_EQUIV_TABLE_NONE};
180 const CSSEditUtils::CSSEquivTable italicEquivTable[] = {
181 {CSSEditUtils::eCSSEditableProperty_font_style, true, false,
182 ProcessDefaultValue, "italic", nullptr, nullptr},
183 CSS_EQUIV_TABLE_NONE};
185 const CSSEditUtils::CSSEquivTable underlineEquivTable[] = {
186 {CSSEditUtils::eCSSEditableProperty_text_decoration, true, false,
187 ProcessDefaultValue, "underline", nullptr, nullptr},
188 CSS_EQUIV_TABLE_NONE};
190 const CSSEditUtils::CSSEquivTable strikeEquivTable[] = {
191 {CSSEditUtils::eCSSEditableProperty_text_decoration, true, false,
192 ProcessDefaultValue, "line-through", nullptr, nullptr},
193 CSS_EQUIV_TABLE_NONE};
195 const CSSEditUtils::CSSEquivTable ttEquivTable[] = {
196 {CSSEditUtils::eCSSEditableProperty_font_family, true, false,
197 ProcessDefaultValue, "monospace", nullptr, nullptr},
198 CSS_EQUIV_TABLE_NONE};
200 const CSSEditUtils::CSSEquivTable fontColorEquivTable[] = {
201 {CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue,
202 nullptr, nullptr, nullptr},
203 CSS_EQUIV_TABLE_NONE};
205 const CSSEditUtils::CSSEquivTable fontFaceEquivTable[] = {
206 {CSSEditUtils::eCSSEditableProperty_font_family, true, false,
207 ProcessSameValue, nullptr, nullptr, nullptr},
208 CSS_EQUIV_TABLE_NONE};
210 const CSSEditUtils::CSSEquivTable bgcolorEquivTable[] = {
211 {CSSEditUtils::eCSSEditableProperty_background_color, true, false,
212 ProcessSameValue, nullptr, nullptr, nullptr},
213 CSS_EQUIV_TABLE_NONE};
215 const CSSEditUtils::CSSEquivTable backgroundImageEquivTable[] = {
216 {CSSEditUtils::eCSSEditableProperty_background_image, true, true,
217 ProcessExtendedValue, nullptr, "url(", ")"},
218 CSS_EQUIV_TABLE_NONE};
220 const CSSEditUtils::CSSEquivTable textColorEquivTable[] = {
221 {CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue,
222 nullptr, nullptr, nullptr},
223 CSS_EQUIV_TABLE_NONE};
225 const CSSEditUtils::CSSEquivTable borderEquivTable[] = {
226 {CSSEditUtils::eCSSEditableProperty_border, true, false,
227 ProcessExtendedValue, nullptr, nullptr, "px solid"},
228 CSS_EQUIV_TABLE_NONE};
230 const CSSEditUtils::CSSEquivTable textAlignEquivTable[] = {
231 {CSSEditUtils::eCSSEditableProperty_text_align, true, false,
232 ProcessSameValue, nullptr, nullptr, nullptr},
233 CSS_EQUIV_TABLE_NONE};
235 const CSSEditUtils::CSSEquivTable captionAlignEquivTable[] = {
236 {CSSEditUtils::eCSSEditableProperty_caption_side, true, false,
237 ProcessSameValue, nullptr, nullptr, nullptr},
238 CSS_EQUIV_TABLE_NONE};
240 const CSSEditUtils::CSSEquivTable verticalAlignEquivTable[] = {
241 {CSSEditUtils::eCSSEditableProperty_vertical_align, true, false,
242 ProcessSameValue, nullptr, nullptr, nullptr},
243 CSS_EQUIV_TABLE_NONE};
245 const CSSEditUtils::CSSEquivTable nowrapEquivTable[] = {
246 {CSSEditUtils::eCSSEditableProperty_whitespace, true, false,
247 ProcessDefaultValue, "nowrap", nullptr, nullptr},
248 CSS_EQUIV_TABLE_NONE};
250 const CSSEditUtils::CSSEquivTable widthEquivTable[] = {
251 {CSSEditUtils::eCSSEditableProperty_width, true, false, ProcessLengthValue,
252 nullptr, nullptr, nullptr},
253 CSS_EQUIV_TABLE_NONE};
255 const CSSEditUtils::CSSEquivTable heightEquivTable[] = {
256 {CSSEditUtils::eCSSEditableProperty_height, true, false, ProcessLengthValue,
257 nullptr, nullptr, nullptr},
258 CSS_EQUIV_TABLE_NONE};
260 const CSSEditUtils::CSSEquivTable listStyleTypeEquivTable[] = {
261 {CSSEditUtils::eCSSEditableProperty_list_style_type, true, true,
262 ProcessListStyleTypeValue, nullptr, nullptr, nullptr},
263 CSS_EQUIV_TABLE_NONE};
265 const CSSEditUtils::CSSEquivTable tableAlignEquivTable[] = {
266 {CSSEditUtils::eCSSEditableProperty_text_align, false, false,
267 ProcessDefaultValue, "left", nullptr, nullptr},
268 {CSSEditUtils::eCSSEditableProperty_margin_left, true, false,
269 ProcessMarginLeftValue, nullptr, nullptr, nullptr},
270 {CSSEditUtils::eCSSEditableProperty_margin_right, true, false,
271 ProcessMarginRightValue, nullptr, nullptr, nullptr},
272 CSS_EQUIV_TABLE_NONE};
274 const CSSEditUtils::CSSEquivTable hrAlignEquivTable[] = {
275 {CSSEditUtils::eCSSEditableProperty_margin_left, true, false,
276 ProcessMarginLeftValue, nullptr, nullptr, nullptr},
277 {CSSEditUtils::eCSSEditableProperty_margin_right, true, false,
278 ProcessMarginRightValue, nullptr, nullptr, nullptr},
279 CSS_EQUIV_TABLE_NONE};
281 #undef CSS_EQUIV_TABLE_NONE
283 CSSEditUtils::CSSEditUtils(HTMLEditor* aHTMLEditor)
284 : mHTMLEditor(aHTMLEditor),
285 mIsCSSPrefChecked(StaticPrefs::editor_use_css()) {}
287 // Answers true if we have some CSS equivalence for the HTML style defined
288 // by aProperty and/or aAttribute for the node aNode
290 // static
291 bool CSSEditUtils::IsCSSEditableProperty(nsINode* aNode, nsAtom* aProperty,
292 nsAtom* aAttribute) {
293 MOZ_ASSERT(aNode);
295 Element* element = aNode->GetAsElementOrParentElement();
296 if (NS_WARN_IF(!element)) {
297 return false;
300 // html inline styles B I TT U STRIKE and COLOR/FACE on FONT
301 if (nsGkAtoms::b == aProperty || nsGkAtoms::i == aProperty ||
302 nsGkAtoms::tt == aProperty || nsGkAtoms::u == aProperty ||
303 nsGkAtoms::strike == aProperty ||
304 (nsGkAtoms::font == aProperty && aAttribute &&
305 (aAttribute == nsGkAtoms::color || aAttribute == nsGkAtoms::face))) {
306 return true;
309 // ALIGN attribute on elements supporting it
310 if (aAttribute == nsGkAtoms::align &&
311 element->IsAnyOfHTMLElements(
312 nsGkAtoms::div, nsGkAtoms::p, nsGkAtoms::h1, nsGkAtoms::h2,
313 nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6,
314 nsGkAtoms::td, nsGkAtoms::th, nsGkAtoms::table, nsGkAtoms::hr,
315 // For the above, why not use
316 // HTMLEditUtils::SupportsAlignAttr?
317 // It also checks for tbody, tfoot, thead.
318 // Let's add the following elements here even
319 // if "align" has a different meaning for them
320 nsGkAtoms::legend, nsGkAtoms::caption)) {
321 return true;
324 if (aAttribute == nsGkAtoms::valign &&
325 element->IsAnyOfHTMLElements(
326 nsGkAtoms::col, nsGkAtoms::colgroup, nsGkAtoms::tbody, nsGkAtoms::td,
327 nsGkAtoms::th, nsGkAtoms::tfoot, nsGkAtoms::thead, nsGkAtoms::tr)) {
328 return true;
331 // attributes TEXT, BACKGROUND and BGCOLOR on BODY
332 if (element->IsHTMLElement(nsGkAtoms::body) &&
333 (aAttribute == nsGkAtoms::text || aAttribute == nsGkAtoms::background ||
334 aAttribute == nsGkAtoms::bgcolor)) {
335 return true;
338 // attribute BGCOLOR on other elements
339 if (aAttribute == nsGkAtoms::bgcolor) {
340 return true;
343 // attributes HEIGHT, WIDTH and NOWRAP on TD and TH
344 if (element->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th) &&
345 (aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::width ||
346 aAttribute == nsGkAtoms::nowrap)) {
347 return true;
350 // attributes HEIGHT and WIDTH on TABLE
351 if (element->IsHTMLElement(nsGkAtoms::table) &&
352 (aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::width)) {
353 return true;
356 // attributes SIZE and WIDTH on HR
357 if (element->IsHTMLElement(nsGkAtoms::hr) &&
358 (aAttribute == nsGkAtoms::size || aAttribute == nsGkAtoms::width)) {
359 return true;
362 // attribute TYPE on OL UL LI
363 if (element->IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul,
364 nsGkAtoms::li) &&
365 aAttribute == nsGkAtoms::type) {
366 return true;
369 if (element->IsHTMLElement(nsGkAtoms::img) &&
370 (aAttribute == nsGkAtoms::border || aAttribute == nsGkAtoms::width ||
371 aAttribute == nsGkAtoms::height)) {
372 return true;
375 // other elements that we can align using CSS even if they
376 // can't carry the html ALIGN attribute
377 if (aAttribute == nsGkAtoms::align &&
378 element->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl,
379 nsGkAtoms::li, nsGkAtoms::dd, nsGkAtoms::dt,
380 nsGkAtoms::address, nsGkAtoms::pre)) {
381 return true;
384 return false;
387 // The lowest level above the transaction; adds the CSS declaration
388 // "aProperty : aValue" to the inline styles carried by aStyledElement
389 nsresult CSSEditUtils::SetCSSPropertyInternal(nsStyledElement& aStyledElement,
390 nsAtom& aProperty,
391 const nsAString& aValue,
392 bool aSuppressTxn) {
393 RefPtr<ChangeStyleTransaction> transaction =
394 ChangeStyleTransaction::Create(aStyledElement, aProperty, aValue);
395 if (aSuppressTxn) {
396 nsresult rv = transaction->DoTransaction();
397 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
398 "ChangeStyleTransaction::DoTransaction() failed");
399 return rv;
401 if (NS_WARN_IF(!mHTMLEditor)) {
402 return NS_ERROR_NOT_AVAILABLE;
404 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
405 nsresult rv = htmlEditor->DoTransactionInternal(transaction);
406 if (NS_WARN_IF(htmlEditor->Destroyed())) {
407 return NS_ERROR_EDITOR_DESTROYED;
409 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
410 "EditorBase::DoTransactionInternal() failed");
411 return rv;
414 nsresult CSSEditUtils::SetCSSPropertyPixelsWithTransaction(
415 nsStyledElement& aStyledElement, nsAtom& aProperty, int32_t aIntValue) {
416 nsAutoString s;
417 s.AppendInt(aIntValue);
418 nsresult rv =
419 SetCSSPropertyWithTransaction(aStyledElement, aProperty, s + u"px"_ns);
420 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
421 "CSSEditUtils::SetCSSPropertyWithTransaction() failed");
422 return rv;
425 nsresult CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(
426 nsStyledElement& aStyledElement, const nsAtom& aProperty,
427 int32_t aIntValue) {
428 nsCOMPtr<nsICSSDeclaration> cssDecl = aStyledElement.Style();
430 nsAutoCString propertyNameString;
431 aProperty.ToUTF8String(propertyNameString);
433 nsAutoCString s;
434 s.AppendInt(aIntValue);
435 s.AppendLiteral("px");
437 ErrorResult error;
438 cssDecl->SetProperty(propertyNameString, s, EmptyCString(), error);
439 if (error.Failed()) {
440 NS_WARNING("nsICSSDeclaration::SetProperty() failed");
441 return error.StealNSResult();
444 return NS_OK;
447 // The lowest level above the transaction; removes the value aValue from the
448 // list of values specified for the CSS property aProperty, or totally remove
449 // the declaration if this property accepts only one value
450 nsresult CSSEditUtils::RemoveCSSPropertyInternal(
451 nsStyledElement& aStyledElement, nsAtom& aProperty, const nsAString& aValue,
452 bool aSuppressTxn) {
453 RefPtr<ChangeStyleTransaction> transaction =
454 ChangeStyleTransaction::CreateToRemove(aStyledElement, aProperty, aValue);
455 if (aSuppressTxn) {
456 nsresult rv = transaction->DoTransaction();
457 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
458 "ChangeStyleTransaction::DoTransaction() failed");
459 return rv;
461 if (NS_WARN_IF(!mHTMLEditor)) {
462 return NS_ERROR_NOT_AVAILABLE;
464 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
465 nsresult rv = htmlEditor->DoTransactionInternal(transaction);
466 if (NS_WARN_IF(htmlEditor->Destroyed())) {
467 return NS_ERROR_EDITOR_DESTROYED;
469 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
470 "EditorBase::DoTransactionInternal() failed");
471 return rv;
474 // static
475 nsresult CSSEditUtils::GetSpecifiedProperty(nsIContent& aContent,
476 nsAtom& aCSSProperty,
477 nsAString& aValue) {
478 nsresult rv =
479 GetSpecifiedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
480 NS_WARNING_ASSERTION(
481 NS_SUCCEEDED(rv),
482 "CSSEditUtils::GeSpecifiedCSSInlinePropertyBase() failed");
483 return rv;
486 // static
487 nsresult CSSEditUtils::GetComputedProperty(nsIContent& aContent,
488 nsAtom& aCSSProperty,
489 nsAString& aValue) {
490 nsresult rv =
491 GetComputedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
492 NS_WARNING_ASSERTION(
493 NS_SUCCEEDED(rv),
494 "CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
495 return rv;
498 // static
499 nsresult CSSEditUtils::GetComputedCSSInlinePropertyBase(nsIContent& aContent,
500 nsAtom& aCSSProperty,
501 nsAString& aValue) {
502 aValue.Truncate();
504 RefPtr<Element> element = aContent.GetAsElementOrParentElement();
505 if (NS_WARN_IF(!element)) {
506 return NS_ERROR_INVALID_ARG;
509 // Get the all the computed css styles attached to the element node
510 RefPtr<nsComputedDOMStyle> computedDOMStyle = GetComputedStyle(element);
511 if (NS_WARN_IF(!computedDOMStyle)) {
512 return NS_ERROR_INVALID_ARG;
515 // from these declarations, get the one we want and that one only
517 // FIXME(bug 1606994): nsAtomCString copies, we should just keep around the
518 // property id.
520 // FIXME: Maybe we can avoid copying aValue too, though it's no worse than
521 // what we used to do.
522 nsAutoCString value;
523 MOZ_ALWAYS_SUCCEEDS(
524 computedDOMStyle->GetPropertyValue(nsAtomCString(&aCSSProperty), value));
525 CopyUTF8toUTF16(value, aValue);
526 return NS_OK;
529 // static
530 nsresult CSSEditUtils::GetSpecifiedCSSInlinePropertyBase(nsIContent& aContent,
531 nsAtom& aCSSProperty,
532 nsAString& aValue) {
533 aValue.Truncate();
535 RefPtr<Element> element = aContent.GetAsElementOrParentElement();
536 if (NS_WARN_IF(!element)) {
537 return NS_ERROR_INVALID_ARG;
540 RefPtr<DeclarationBlock> decl = element->GetInlineStyleDeclaration();
541 if (!decl) {
542 return NS_OK;
545 // FIXME: Same comments as above.
546 nsCSSPropertyID prop =
547 nsCSSProps::LookupProperty(nsAtomCString(&aCSSProperty));
548 MOZ_ASSERT(prop != eCSSProperty_UNKNOWN);
550 nsAutoCString value;
551 decl->GetPropertyValueByID(prop, value);
552 CopyUTF8toUTF16(value, aValue);
553 return NS_OK;
556 // static
557 already_AddRefed<nsComputedDOMStyle> CSSEditUtils::GetComputedStyle(
558 Element* aElement) {
559 MOZ_ASSERT(aElement);
561 Document* document = aElement->GetComposedDoc();
562 if (NS_WARN_IF(!document)) {
563 return nullptr;
566 RefPtr<nsComputedDOMStyle> computedDOMStyle = NS_NewComputedDOMStyle(
567 aElement, u""_ns, document, nsComputedDOMStyle::StyleType::All,
568 IgnoreErrors());
569 return computedDOMStyle.forget();
572 // remove the CSS style "aProperty : aPropertyValue" and possibly remove the
573 // whole node if it is a span and if its only attribute is _moz_dirty
574 nsresult CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
575 nsStyledElement& aStyledElement, nsAtom* aProperty,
576 const nsAString& aPropertyValue) {
577 // remove the property from the style attribute
578 nsresult rv = RemoveCSSPropertyWithTransaction(aStyledElement, *aProperty,
579 aPropertyValue);
580 if (NS_FAILED(rv)) {
581 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
582 return rv;
585 if (!aStyledElement.IsHTMLElement(nsGkAtoms::span) ||
586 HTMLEditor::HasAttributes(&aStyledElement)) {
587 return NS_OK;
590 OwningNonNull<HTMLEditor> htmlEditor(*mHTMLEditor);
591 const Result<EditorDOMPoint, nsresult> unwrapStyledElementResult =
592 htmlEditor->RemoveContainerWithTransaction(aStyledElement);
593 if (MOZ_UNLIKELY(unwrapStyledElementResult.isErr())) {
594 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed");
595 return unwrapStyledElementResult.inspectErr();
597 const EditorDOMPoint& pointToPutCaret = unwrapStyledElementResult.inspect();
598 if (!htmlEditor->AllowsTransactionsToChangeSelection() ||
599 !pointToPutCaret.IsSet()) {
600 return NS_OK;
602 rv = htmlEditor->CollapseSelectionTo(pointToPutCaret);
603 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
604 "EditorBase::CollapseSelectionTo() failed");
605 return rv;
608 // Answers true if the property can be removed by setting a "none" CSS value
609 // on a node
611 // static
612 bool CSSEditUtils::IsCSSInvertible(nsAtom& aProperty, nsAtom* aAttribute) {
613 return nsGkAtoms::b == &aProperty;
616 // Get the default browser background color if we need it for
617 // GetCSSBackgroundColorState
619 // static
620 void CSSEditUtils::GetDefaultBackgroundColor(nsAString& aColor) {
621 if (MOZ_UNLIKELY(StaticPrefs::editor_use_custom_colors())) {
622 nsresult rv = Preferences::GetString("editor.background_color", aColor);
623 // XXX Why don't you validate the pref value?
624 if (NS_FAILED(rv)) {
625 NS_WARNING("failed to get editor.background_color");
626 aColor.AssignLiteral("#ffffff"); // Default to white
628 return;
631 if (Preferences::GetBool("browser.display.use_system_colors", false)) {
632 return;
635 nsresult rv =
636 Preferences::GetString("browser.display.background_color", aColor);
637 // XXX Why don't you validate the pref value?
638 if (NS_FAILED(rv)) {
639 NS_WARNING("failed to get browser.display.background_color");
640 aColor.AssignLiteral("#ffffff"); // Default to white
644 // Get the default length unit used for CSS Indent/Outdent
646 // static
647 void CSSEditUtils::GetDefaultLengthUnit(nsAString& aLengthUnit) {
648 // XXX Why don't you validate the pref value?
649 if (MOZ_UNLIKELY(NS_FAILED(Preferences::GetString(
650 "editor.css.default_length_unit", aLengthUnit)))) {
651 aLengthUnit.AssignLiteral("px");
655 // static
656 void CSSEditUtils::ParseLength(const nsAString& aString, float* aValue,
657 nsAtom** aUnit) {
658 if (aString.IsEmpty()) {
659 *aValue = 0;
660 *aUnit = NS_Atomize(aString).take();
661 return;
664 nsAString::const_iterator iter;
665 aString.BeginReading(iter);
667 float a = 10.0f, b = 1.0f, value = 0;
668 int8_t sign = 1;
669 int32_t i = 0, j = aString.Length();
670 char16_t c;
671 bool floatingPointFound = false;
672 c = *iter;
673 if (char16_t('-') == c) {
674 sign = -1;
675 iter++;
676 i++;
677 } else if (char16_t('+') == c) {
678 iter++;
679 i++;
681 while (i < j) {
682 c = *iter;
683 if ((char16_t('0') == c) || (char16_t('1') == c) || (char16_t('2') == c) ||
684 (char16_t('3') == c) || (char16_t('4') == c) || (char16_t('5') == c) ||
685 (char16_t('6') == c) || (char16_t('7') == c) || (char16_t('8') == c) ||
686 (char16_t('9') == c)) {
687 value = (value * a) + (b * (c - char16_t('0')));
688 b = b / 10 * a;
689 } else if (!floatingPointFound && (char16_t('.') == c)) {
690 floatingPointFound = true;
691 a = 1.0f;
692 b = 0.1f;
693 } else
694 break;
695 iter++;
696 i++;
698 *aValue = value * sign;
699 *aUnit = NS_Atomize(StringTail(aString, j - i)).take();
702 // static
703 nsStaticAtom* CSSEditUtils::GetCSSPropertyAtom(
704 nsCSSEditableProperty aProperty) {
705 switch (aProperty) {
706 case eCSSEditableProperty_background_color:
707 return nsGkAtoms::backgroundColor;
708 case eCSSEditableProperty_background_image:
709 return nsGkAtoms::background_image;
710 case eCSSEditableProperty_border:
711 return nsGkAtoms::border;
712 case eCSSEditableProperty_caption_side:
713 return nsGkAtoms::caption_side;
714 case eCSSEditableProperty_color:
715 return nsGkAtoms::color;
716 case eCSSEditableProperty_float:
717 return nsGkAtoms::_float;
718 case eCSSEditableProperty_font_family:
719 return nsGkAtoms::font_family;
720 case eCSSEditableProperty_font_size:
721 return nsGkAtoms::font_size;
722 case eCSSEditableProperty_font_style:
723 return nsGkAtoms::font_style;
724 case eCSSEditableProperty_font_weight:
725 return nsGkAtoms::fontWeight;
726 case eCSSEditableProperty_height:
727 return nsGkAtoms::height;
728 case eCSSEditableProperty_list_style_type:
729 return nsGkAtoms::list_style_type;
730 case eCSSEditableProperty_margin_left:
731 return nsGkAtoms::marginLeft;
732 case eCSSEditableProperty_margin_right:
733 return nsGkAtoms::marginRight;
734 case eCSSEditableProperty_text_align:
735 return nsGkAtoms::textAlign;
736 case eCSSEditableProperty_text_decoration:
737 return nsGkAtoms::text_decoration;
738 case eCSSEditableProperty_vertical_align:
739 return nsGkAtoms::vertical_align;
740 case eCSSEditableProperty_whitespace:
741 return nsGkAtoms::white_space;
742 case eCSSEditableProperty_width:
743 return nsGkAtoms::width;
744 case eCSSEditableProperty_NONE:
745 // intentionally empty
746 return nullptr;
748 MOZ_ASSERT_UNREACHABLE("Got unknown property");
749 return nullptr;
752 // Populate aOutArrayOfCSSProperty and aOutArrayOfCSSValue with the CSS
753 // declarations equivalent to the value aValue according to the equivalence
754 // table aEquivTable
756 // static
757 void CSSEditUtils::BuildCSSDeclarations(
758 nsTArray<nsStaticAtom*>& aOutArrayOfCSSProperty,
759 nsTArray<nsString>& aOutArrayOfCSSValue, const CSSEquivTable* aEquivTable,
760 const nsAString* aValue, bool aGetOrRemoveRequest) {
761 // clear arrays
762 aOutArrayOfCSSProperty.Clear();
763 aOutArrayOfCSSValue.Clear();
765 // if we have an input value, let's use it
766 nsAutoString value, lowerCasedValue;
767 if (aValue) {
768 value.Assign(*aValue);
769 lowerCasedValue.Assign(*aValue);
770 ToLowerCase(lowerCasedValue);
773 int8_t index = 0;
774 nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty;
775 while (cssProperty) {
776 if (!aGetOrRemoveRequest || aEquivTable[index].gettable) {
777 nsAutoString cssValue, cssPropertyString;
778 // find the equivalent css value for the index-th property in
779 // the equivalence table
780 (*aEquivTable[index].processValueFunctor)(
781 (!aGetOrRemoveRequest || aEquivTable[index].caseSensitiveValue)
782 ? &value
783 : &lowerCasedValue,
784 cssValue, aEquivTable[index].defaultValue,
785 aEquivTable[index].prependValue, aEquivTable[index].appendValue);
786 aOutArrayOfCSSProperty.AppendElement(GetCSSPropertyAtom(cssProperty));
787 aOutArrayOfCSSValue.AppendElement(cssValue);
789 index++;
790 cssProperty = aEquivTable[index].cssProperty;
794 // Populate aOutArrayOfCSSProperty and aOutArrayOfCSSValue with the declarations
795 // equivalent to aHTMLProperty/aAttribute/aValue for the node aNode
797 // static
798 void CSSEditUtils::GenerateCSSDeclarationsFromHTMLStyle(
799 Element& aElement, nsAtom* aHTMLProperty, nsAtom* aAttribute,
800 const nsAString* aValue, nsTArray<nsStaticAtom*>& aOutArrayOfCSSProperty,
801 nsTArray<nsString>& aOutArrayOfCSSValue, bool aGetOrRemoveRequest) {
802 const CSSEditUtils::CSSEquivTable* equivTable = nullptr;
804 if (nsGkAtoms::b == aHTMLProperty) {
805 equivTable = boldEquivTable;
806 } else if (nsGkAtoms::i == aHTMLProperty) {
807 equivTable = italicEquivTable;
808 } else if (nsGkAtoms::u == aHTMLProperty) {
809 equivTable = underlineEquivTable;
810 } else if (nsGkAtoms::strike == aHTMLProperty) {
811 equivTable = strikeEquivTable;
812 } else if (nsGkAtoms::tt == aHTMLProperty) {
813 equivTable = ttEquivTable;
814 } else if (aAttribute) {
815 if (nsGkAtoms::font == aHTMLProperty && aAttribute == nsGkAtoms::color) {
816 equivTable = fontColorEquivTable;
817 } else if (nsGkAtoms::font == aHTMLProperty &&
818 aAttribute == nsGkAtoms::face) {
819 equivTable = fontFaceEquivTable;
820 } else if (aAttribute == nsGkAtoms::bgcolor) {
821 equivTable = bgcolorEquivTable;
822 } else if (aAttribute == nsGkAtoms::background) {
823 equivTable = backgroundImageEquivTable;
824 } else if (aAttribute == nsGkAtoms::text) {
825 equivTable = textColorEquivTable;
826 } else if (aAttribute == nsGkAtoms::border) {
827 equivTable = borderEquivTable;
828 } else if (aAttribute == nsGkAtoms::align) {
829 if (aElement.IsHTMLElement(nsGkAtoms::table)) {
830 equivTable = tableAlignEquivTable;
831 } else if (aElement.IsHTMLElement(nsGkAtoms::hr)) {
832 equivTable = hrAlignEquivTable;
833 } else if (aElement.IsAnyOfHTMLElements(nsGkAtoms::legend,
834 nsGkAtoms::caption)) {
835 equivTable = captionAlignEquivTable;
836 } else {
837 equivTable = textAlignEquivTable;
839 } else if (aAttribute == nsGkAtoms::valign) {
840 equivTable = verticalAlignEquivTable;
841 } else if (aAttribute == nsGkAtoms::nowrap) {
842 equivTable = nowrapEquivTable;
843 } else if (aAttribute == nsGkAtoms::width) {
844 equivTable = widthEquivTable;
845 } else if (aAttribute == nsGkAtoms::height ||
846 (aElement.IsHTMLElement(nsGkAtoms::hr) &&
847 aAttribute == nsGkAtoms::size)) {
848 equivTable = heightEquivTable;
849 } else if (aAttribute == nsGkAtoms::type &&
850 aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul,
851 nsGkAtoms::li)) {
852 equivTable = listStyleTypeEquivTable;
855 if (equivTable) {
856 BuildCSSDeclarations(aOutArrayOfCSSProperty, aOutArrayOfCSSValue,
857 equivTable, aValue, aGetOrRemoveRequest);
861 // Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/
862 // aValue for the node, and return in aCount the number of CSS properties set
863 // by the call. The Element version returns aCount instead.
864 Result<int32_t, nsresult> CSSEditUtils::SetCSSEquivalentToHTMLStyleInternal(
865 nsStyledElement& aStyledElement, nsAtom* aHTMLProperty, nsAtom* aAttribute,
866 const nsAString* aValue, bool aSuppressTransaction) {
867 if (!IsCSSEditableProperty(&aStyledElement, aHTMLProperty, aAttribute)) {
868 return 0;
871 // we can apply the styles only if the node is an element and if we have
872 // an equivalence for the requested HTML style in this implementation
874 // Find the CSS equivalence to the HTML style
875 nsTArray<nsStaticAtom*> cssPropertyArray;
876 nsTArray<nsString> cssValueArray;
877 GenerateCSSDeclarationsFromHTMLStyle(aStyledElement, aHTMLProperty,
878 aAttribute, aValue, cssPropertyArray,
879 cssValueArray, false);
881 // set the individual CSS inline styles
882 const size_t count = cssPropertyArray.Length();
883 for (size_t index = 0; index < count; index++) {
884 nsresult rv = SetCSSPropertyInternal(
885 aStyledElement, MOZ_KnownLive(*cssPropertyArray[index]),
886 cssValueArray[index], aSuppressTransaction);
887 if (NS_FAILED(rv)) {
888 NS_WARNING("CSSEditUtils::SetCSSPropertyInternal() failed");
889 return Err(rv);
892 return count;
895 // Remove from aNode the CSS inline style equivalent to
896 // HTMLProperty/aAttribute/aValue for the node
897 nsresult CSSEditUtils::RemoveCSSEquivalentToHTMLStyleInternal(
898 nsStyledElement& aStyledElement, nsAtom* aHTMLProperty, nsAtom* aAttribute,
899 const nsAString* aValue, bool aSuppressTransaction) {
900 if (!IsCSSEditableProperty(&aStyledElement, aHTMLProperty, aAttribute)) {
901 return NS_OK;
904 // we can apply the styles only if the node is an element and if we have
905 // an equivalence for the requested HTML style in this implementation
907 // Find the CSS equivalence to the HTML style
908 nsTArray<nsStaticAtom*> cssPropertyArray;
909 nsTArray<nsString> cssValueArray;
910 GenerateCSSDeclarationsFromHTMLStyle(aStyledElement, aHTMLProperty,
911 aAttribute, aValue, cssPropertyArray,
912 cssValueArray, true);
914 // remove the individual CSS inline styles
915 const size_t count = cssPropertyArray.Length();
916 if (!count) {
917 return NS_OK;
919 for (size_t index = 0; index < count; index++) {
920 nsresult rv = RemoveCSSPropertyInternal(
921 aStyledElement, MOZ_KnownLive(*cssPropertyArray[index]),
922 cssValueArray[index], aSuppressTransaction);
923 if (NS_FAILED(rv)) {
924 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithoutTransaction() failed");
925 return rv;
928 return NS_OK;
931 // returns in aValue the list of values for the CSS equivalences to
932 // the HTML style aHTMLProperty/aAttribute/aValue for the node aNode;
933 // the value of aStyleType controls the styles we retrieve : specified or
934 // computed.
936 // static
937 nsresult CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal(
938 nsIContent& aContent, nsAtom* aHTMLProperty, nsAtom* aAttribute,
939 nsAString& aValue, StyleType aStyleType) {
940 MOZ_ASSERT(aHTMLProperty || aAttribute);
942 aValue.Truncate();
943 RefPtr<Element> theElement = aContent.GetAsElementOrParentElement();
944 if (NS_WARN_IF(!theElement)) {
945 return NS_ERROR_INVALID_ARG;
948 if (!theElement ||
949 !IsCSSEditableProperty(theElement, aHTMLProperty, aAttribute)) {
950 return NS_OK;
953 // Yes, the requested HTML style has a CSS equivalence in this implementation
954 nsTArray<nsStaticAtom*> cssPropertyArray;
955 nsTArray<nsString> cssValueArray;
956 // get the CSS equivalence with last param true indicating we want only the
957 // "gettable" properties
958 GenerateCSSDeclarationsFromHTMLStyle(*theElement, aHTMLProperty, aAttribute,
959 nullptr, cssPropertyArray, cssValueArray,
960 true);
961 int32_t count = cssPropertyArray.Length();
962 for (int32_t index = 0; index < count; index++) {
963 nsAutoString valueString;
964 // retrieve the specified/computed value of the property
965 if (aStyleType == StyleType::Computed) {
966 nsresult rv = GetComputedCSSInlinePropertyBase(
967 *theElement, MOZ_KnownLive(*cssPropertyArray[index]), valueString);
968 if (NS_FAILED(rv)) {
969 NS_WARNING("CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
970 return rv;
972 } else {
973 nsresult rv = GetSpecifiedCSSInlinePropertyBase(
974 *theElement, *cssPropertyArray[index], valueString);
975 if (NS_FAILED(rv)) {
976 NS_WARNING("CSSEditUtils::GetSpecifiedCSSInlinePropertyBase() failed");
977 return rv;
980 // append the value to aValue (possibly with a leading white-space)
981 if (index) {
982 aValue.Append(HTMLEditUtils::kSpace);
984 aValue.Append(valueString);
986 return NS_OK;
989 // Does the node aContent (or its parent, if it's not an element node) have a
990 // CSS style equivalent to the HTML style
991 // aHTMLProperty/aAttribute/valueString? The value of aStyleType controls
992 // the styles we retrieve: specified or computed. The return value aIsSet is
993 // true if the CSS styles are set.
995 // The nsIContent variant returns aIsSet instead of using an out parameter, and
996 // does not modify aValue.
998 Result<bool, nsresult>
999 CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSetInternal(
1000 nsIContent& aContent, nsAtom* aHTMLProperty, nsAtom* aAttribute,
1001 nsAString& aValue, StyleType aStyleType) {
1002 MOZ_ASSERT(aHTMLProperty || aAttribute);
1004 nsAutoString htmlValueString(aValue);
1005 bool isSet = false;
1006 // FYI: Cannot use InclusiveAncestorsOfType here because
1007 // GetCSSEquivalentToHTMLInlineStyleSetInternal() may flush pending
1008 // notifications.
1009 for (nsCOMPtr<nsIContent> content = &aContent; content;
1010 content = content->GetParentElement()) {
1011 nsCOMPtr<nsINode> parentNode = content->GetParentNode();
1012 aValue.Assign(htmlValueString);
1013 // get the value of the CSS equivalent styles
1014 nsresult rv = GetCSSEquivalentToHTMLInlineStyleSetInternal(
1015 *content, aHTMLProperty, aAttribute, aValue, aStyleType);
1016 if (NS_WARN_IF(!mHTMLEditor || mHTMLEditor->Destroyed())) {
1017 return Err(NS_ERROR_EDITOR_DESTROYED);
1019 if (NS_FAILED(rv)) {
1020 NS_WARNING(
1021 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
1022 "failed");
1023 return Err(rv);
1025 if (NS_WARN_IF(parentNode != content->GetParentNode())) {
1026 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1029 // early way out if we can
1030 if (aValue.IsEmpty()) {
1031 return isSet;
1034 if (nsGkAtoms::b == aHTMLProperty) {
1035 if (aValue.EqualsLiteral("bold")) {
1036 isSet = true;
1037 } else if (aValue.EqualsLiteral("normal")) {
1038 isSet = false;
1039 } else if (aValue.EqualsLiteral("bolder")) {
1040 isSet = true;
1041 aValue.AssignLiteral("bold");
1042 } else {
1043 int32_t weight = 0;
1044 nsresult rvIgnored;
1045 nsAutoString value(aValue);
1046 weight = value.ToInteger(&rvIgnored);
1047 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1048 "nsAString::ToInteger() failed, but ignored");
1049 if (400 < weight) {
1050 isSet = true;
1051 aValue.AssignLiteral("bold");
1052 } else {
1053 isSet = false;
1054 aValue.AssignLiteral("normal");
1057 } else if (nsGkAtoms::i == aHTMLProperty) {
1058 if (aValue.EqualsLiteral("italic") || aValue.EqualsLiteral("oblique")) {
1059 isSet = true;
1061 } else if (nsGkAtoms::u == aHTMLProperty) {
1062 isSet = ChangeStyleTransaction::ValueIncludes(
1063 NS_ConvertUTF16toUTF8(aValue), "underline"_ns);
1064 } else if (nsGkAtoms::strike == aHTMLProperty) {
1065 isSet = ChangeStyleTransaction::ValueIncludes(
1066 NS_ConvertUTF16toUTF8(aValue), "line-through"_ns);
1067 } else if ((nsGkAtoms::font == aHTMLProperty &&
1068 aAttribute == nsGkAtoms::color) ||
1069 aAttribute == nsGkAtoms::bgcolor) {
1070 if (htmlValueString.IsEmpty()) {
1071 isSet = true;
1072 } else {
1073 nscolor rgba;
1074 nsAutoString subStr;
1075 htmlValueString.Right(subStr, htmlValueString.Length() - 1);
1076 if (NS_ColorNameToRGB(htmlValueString, &rgba) ||
1077 NS_HexToRGBA(subStr, nsHexColorType::NoAlpha, &rgba)) {
1078 nsAutoString htmlColor, tmpStr;
1080 if (NS_GET_A(rgba) != 255) {
1081 // This should only be hit by the "transparent" keyword, which
1082 // currently serializes to "transparent" (not "rgba(0, 0, 0, 0)").
1083 MOZ_ASSERT(NS_GET_R(rgba) == 0 && NS_GET_G(rgba) == 0 &&
1084 NS_GET_B(rgba) == 0 && NS_GET_A(rgba) == 0);
1085 htmlColor.AppendLiteral("transparent");
1086 } else {
1087 htmlColor.AppendLiteral("rgb(");
1089 constexpr auto comma = u", "_ns;
1091 tmpStr.AppendInt(NS_GET_R(rgba), 10);
1092 htmlColor.Append(tmpStr + comma);
1094 tmpStr.Truncate();
1095 tmpStr.AppendInt(NS_GET_G(rgba), 10);
1096 htmlColor.Append(tmpStr + comma);
1098 tmpStr.Truncate();
1099 tmpStr.AppendInt(NS_GET_B(rgba), 10);
1100 htmlColor.Append(tmpStr);
1102 htmlColor.Append(char16_t(')'));
1105 isSet = htmlColor.Equals(aValue, nsCaseInsensitiveStringComparator);
1106 } else {
1107 isSet =
1108 htmlValueString.Equals(aValue, nsCaseInsensitiveStringComparator);
1111 } else if (nsGkAtoms::tt == aHTMLProperty) {
1112 isSet = StringBeginsWith(aValue, u"monospace"_ns);
1113 } else if (nsGkAtoms::font == aHTMLProperty && aAttribute &&
1114 aAttribute == nsGkAtoms::face) {
1115 if (!htmlValueString.IsEmpty()) {
1116 const char16_t commaSpace[] = {char16_t(','), HTMLEditUtils::kSpace, 0};
1117 const char16_t comma[] = {char16_t(','), 0};
1118 htmlValueString.ReplaceSubstring(commaSpace, comma);
1119 nsAutoString valueStringNorm(aValue);
1120 valueStringNorm.ReplaceSubstring(commaSpace, comma);
1121 isSet = htmlValueString.Equals(valueStringNorm,
1122 nsCaseInsensitiveStringComparator);
1123 } else {
1124 isSet = true;
1126 return isSet;
1127 } else if (aAttribute == nsGkAtoms::align) {
1128 isSet = true;
1129 } else {
1130 return false;
1133 if (!htmlValueString.IsEmpty() &&
1134 htmlValueString.Equals(aValue, nsCaseInsensitiveStringComparator)) {
1135 isSet = true;
1138 if (htmlValueString.EqualsLiteral("-moz-editor-invert-value")) {
1139 isSet = !isSet;
1142 if (isSet) {
1143 return true;
1146 if (nsGkAtoms::u != aHTMLProperty && nsGkAtoms::strike != aHTMLProperty) {
1147 return isSet;
1150 // Unfortunately, the value of the text-decoration property is not
1151 // inherited. that means that we have to look at ancestors of node to see
1152 // if they are underlined.
1154 return isSet;
1157 Result<bool, nsresult> CSSEditUtils::HaveCSSEquivalentStylesInternal(
1158 nsIContent& aContent, nsAtom* aHTMLProperty, nsAtom* aAttribute,
1159 StyleType aStyleType) {
1160 MOZ_ASSERT(aHTMLProperty || aAttribute);
1162 // FYI: Unfortunately, we cannot use InclusiveAncestorsOfType here
1163 // because GetCSSEquivalentToHTMLInlineStyleSetInternal() may flush
1164 // pending notifications.
1165 nsAutoString valueString;
1166 for (nsCOMPtr<nsIContent> content = &aContent; content;
1167 content = content->GetParentElement()) {
1168 nsCOMPtr<nsINode> parentNode = content->GetParentNode();
1169 // get the value of the CSS equivalent styles
1170 nsresult rv = GetCSSEquivalentToHTMLInlineStyleSetInternal(
1171 *content, aHTMLProperty, aAttribute, valueString, aStyleType);
1172 if (NS_WARN_IF(!mHTMLEditor || mHTMLEditor->Destroyed())) {
1173 return Err(NS_ERROR_EDITOR_DESTROYED);
1175 if (NS_FAILED(rv)) {
1176 NS_WARNING(
1177 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
1178 "failed");
1179 return Err(rv);
1181 if (NS_WARN_IF(parentNode != content->GetParentNode())) {
1182 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
1185 if (!valueString.IsEmpty()) {
1186 return true;
1189 if (nsGkAtoms::u != aHTMLProperty && nsGkAtoms::strike != aHTMLProperty) {
1190 return false;
1193 // 'nfortunately, the value of the text-decoration property is not
1194 // inherited.
1195 // that means that we have to look at ancestors of node to see if they
1196 // are underlined.
1199 return false;
1202 void CSSEditUtils::SetCSSEnabled(bool aIsCSSPrefChecked) {
1203 mIsCSSPrefChecked = aIsCSSPrefChecked;
1206 bool CSSEditUtils::IsCSSPrefChecked() const { return mIsCSSPrefChecked; }
1208 // ElementsSameStyle compares two elements and checks if they have the same
1209 // specified CSS declarations in the STYLE attribute
1210 // The answer is always negative if at least one of them carries an ID or a
1211 // class
1213 // static
1214 bool CSSEditUtils::DoStyledElementsHaveSameStyle(
1215 nsStyledElement& aStyledElement, nsStyledElement& aOtherStyledElement) {
1216 if (aStyledElement.HasAttr(kNameSpaceID_None, nsGkAtoms::id) ||
1217 aOtherStyledElement.HasAttr(kNameSpaceID_None, nsGkAtoms::id)) {
1218 // at least one of the spans carries an ID ; suspect a CSS rule applies to
1219 // it and refuse to merge the nodes
1220 return false;
1223 nsAutoString firstClass, otherClass;
1224 bool isElementClassSet =
1225 aStyledElement.GetAttr(kNameSpaceID_None, nsGkAtoms::_class, firstClass);
1226 bool isOtherElementClassSet = aOtherStyledElement.GetAttr(
1227 kNameSpaceID_None, nsGkAtoms::_class, otherClass);
1228 if (isElementClassSet && isOtherElementClassSet) {
1229 // both spans carry a class, let's compare them
1230 if (!firstClass.Equals(otherClass)) {
1231 // WARNING : technically, the comparison just above is questionable :
1232 // from a pure HTML/CSS point of view class="a b" is NOT the same than
1233 // class="b a" because a CSS rule could test the exact value of the class
1234 // attribute to be "a b" for instance ; from a user's point of view, a
1235 // wysiwyg editor should probably NOT make any difference. CSS people
1236 // need to discuss this issue before any modification.
1237 return false;
1239 } else if (isElementClassSet || isOtherElementClassSet) {
1240 // one span only carries a class, early way out
1241 return false;
1244 // XXX If `GetPropertyValue()` won't run script, we can stop using
1245 // nsCOMPtr here.
1246 nsCOMPtr<nsICSSDeclaration> firstCSSDecl = aStyledElement.Style();
1247 if (!firstCSSDecl) {
1248 NS_WARNING("nsStyledElement::Style() failed");
1249 return false;
1251 nsCOMPtr<nsICSSDeclaration> otherCSSDecl = aOtherStyledElement.Style();
1252 if (!otherCSSDecl) {
1253 NS_WARNING("nsStyledElement::Style() failed");
1254 return false;
1257 const uint32_t firstLength = firstCSSDecl->Length();
1258 const uint32_t otherLength = otherCSSDecl->Length();
1259 if (firstLength != otherLength) {
1260 // early way out if we can
1261 return false;
1264 if (!firstLength) {
1265 // no inline style !
1266 return true;
1269 for (uint32_t i = 0; i < firstLength; i++) {
1270 nsAutoCString firstValue, otherValue;
1271 nsAutoCString propertyNameString;
1272 firstCSSDecl->Item(i, propertyNameString);
1273 DebugOnly<nsresult> rvIgnored =
1274 firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
1275 NS_WARNING_ASSERTION(
1276 NS_SUCCEEDED(rvIgnored),
1277 "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
1278 rvIgnored = otherCSSDecl->GetPropertyValue(propertyNameString, otherValue);
1279 NS_WARNING_ASSERTION(
1280 NS_SUCCEEDED(rvIgnored),
1281 "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
1282 if (!firstValue.Equals(otherValue)) {
1283 return false;
1286 for (uint32_t i = 0; i < otherLength; i++) {
1287 nsAutoCString firstValue, otherValue;
1288 nsAutoCString propertyNameString;
1289 otherCSSDecl->Item(i, propertyNameString);
1290 DebugOnly<nsresult> rvIgnored =
1291 otherCSSDecl->GetPropertyValue(propertyNameString, otherValue);
1292 NS_WARNING_ASSERTION(
1293 NS_SUCCEEDED(rvIgnored),
1294 "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
1295 rvIgnored = firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
1296 NS_WARNING_ASSERTION(
1297 NS_SUCCEEDED(rvIgnored),
1298 "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
1299 if (!firstValue.Equals(otherValue)) {
1300 return false;
1304 return true;
1307 } // namespace mozilla