summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/CSSEditUtils.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:44:51 +0000
commit9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /editor/libeditor/CSSEditUtils.cpp
parentInitial commit. (diff)
downloadthunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.tar.xz
thunderbird-9e3c08db40b8916968b9f30096c7be3f00ce9647.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'editor/libeditor/CSSEditUtils.cpp')
-rw-r--r--editor/libeditor/CSSEditUtils.cpp1320
1 files changed, 1320 insertions, 0 deletions
diff --git a/editor/libeditor/CSSEditUtils.cpp b/editor/libeditor/CSSEditUtils.cpp
new file mode 100644
index 0000000000..fedd23d955
--- /dev/null
+++ b/editor/libeditor/CSSEditUtils.cpp
@@ -0,0 +1,1320 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CSSEditUtils.h"
+
+#include "ChangeStyleTransaction.h"
+#include "HTMLEditHelpers.h"
+#include "HTMLEditor.h"
+#include "HTMLEditUtils.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DeclarationBlock.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ServoCSSParser.h"
+#include "mozilla/StaticPrefs_editor.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+#include "nsAString.h"
+#include "nsCOMPtr.h"
+#include "nsCSSProps.h"
+#include "nsColor.h"
+#include "nsComputedDOMStyle.h"
+#include "nsDebug.h"
+#include "nsDependentSubstring.h"
+#include "nsError.h"
+#include "nsGkAtoms.h"
+#include "nsAtom.h"
+#include "nsIContent.h"
+#include "nsICSSDeclaration.h"
+#include "nsINode.h"
+#include "nsISupportsImpl.h"
+#include "nsISupportsUtils.h"
+#include "nsLiteralString.h"
+#include "nsPIDOMWindow.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsStringIterator.h"
+#include "nsStyledElement.h"
+#include "nsUnicharUtils.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+static void ProcessBValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ if (aInputString && aInputString->EqualsLiteral("-moz-editor-invert-value")) {
+ aOutputString.AssignLiteral("normal");
+ } else {
+ aOutputString.AssignLiteral("bold");
+ }
+}
+
+static void ProcessDefaultValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ CopyASCIItoUTF16(MakeStringSpan(aDefaultValueString), aOutputString);
+}
+
+static void ProcessSameValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ if (aInputString) {
+ aOutputString.Assign(*aInputString);
+ } else
+ aOutputString.Truncate();
+}
+
+static void ProcessExtendedValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ aOutputString.Truncate();
+ if (aInputString) {
+ if (aPrependString) {
+ AppendASCIItoUTF16(MakeStringSpan(aPrependString), aOutputString);
+ }
+ aOutputString.Append(*aInputString);
+ if (aAppendString) {
+ AppendASCIItoUTF16(MakeStringSpan(aAppendString), aOutputString);
+ }
+ }
+}
+
+static void ProcessLengthValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ aOutputString.Truncate();
+ if (aInputString) {
+ aOutputString.Append(*aInputString);
+ if (-1 == aOutputString.FindChar(char16_t('%'))) {
+ aOutputString.AppendLiteral("px");
+ }
+ }
+}
+
+static void ProcessListStyleTypeValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ aOutputString.Truncate();
+ if (aInputString) {
+ if (aInputString->EqualsLiteral("1")) {
+ aOutputString.AppendLiteral("decimal");
+ } else if (aInputString->EqualsLiteral("a")) {
+ aOutputString.AppendLiteral("lower-alpha");
+ } else if (aInputString->EqualsLiteral("A")) {
+ aOutputString.AppendLiteral("upper-alpha");
+ } else if (aInputString->EqualsLiteral("i")) {
+ aOutputString.AppendLiteral("lower-roman");
+ } else if (aInputString->EqualsLiteral("I")) {
+ aOutputString.AppendLiteral("upper-roman");
+ } else if (aInputString->EqualsLiteral("square") ||
+ aInputString->EqualsLiteral("circle") ||
+ aInputString->EqualsLiteral("disc")) {
+ aOutputString.Append(*aInputString);
+ }
+ }
+}
+
+static void ProcessMarginLeftValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ aOutputString.Truncate();
+ if (aInputString) {
+ if (aInputString->EqualsLiteral("center") ||
+ aInputString->EqualsLiteral("-moz-center")) {
+ aOutputString.AppendLiteral("auto");
+ } else if (aInputString->EqualsLiteral("right") ||
+ aInputString->EqualsLiteral("-moz-right")) {
+ aOutputString.AppendLiteral("auto");
+ } else {
+ aOutputString.AppendLiteral("0px");
+ }
+ }
+}
+
+static void ProcessMarginRightValue(const nsAString* aInputString,
+ nsAString& aOutputString,
+ const char* aDefaultValueString,
+ const char* aPrependString,
+ const char* aAppendString) {
+ aOutputString.Truncate();
+ if (aInputString) {
+ if (aInputString->EqualsLiteral("center") ||
+ aInputString->EqualsLiteral("-moz-center")) {
+ aOutputString.AppendLiteral("auto");
+ } else if (aInputString->EqualsLiteral("left") ||
+ aInputString->EqualsLiteral("-moz-left")) {
+ aOutputString.AppendLiteral("auto");
+ } else {
+ aOutputString.AppendLiteral("0px");
+ }
+ }
+}
+
+#define CSS_EQUIV_TABLE_NONE \
+ { CSSEditUtils::eCSSEditableProperty_NONE, 0 }
+
+const CSSEditUtils::CSSEquivTable boldEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_font_weight, true, false, ProcessBValue,
+ nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable italicEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_font_style, true, false,
+ ProcessDefaultValue, "italic", nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable underlineEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_text_decoration, true, false,
+ ProcessDefaultValue, "underline", nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable strikeEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_text_decoration, true, false,
+ ProcessDefaultValue, "line-through", nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable ttEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_font_family, true, false,
+ ProcessDefaultValue, "monospace", nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable fontColorEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue,
+ nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable fontFaceEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_font_family, true, false,
+ ProcessSameValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable fontSizeEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_font_size, true, false,
+ ProcessSameValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable bgcolorEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_background_color, true, false,
+ ProcessSameValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable backgroundImageEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_background_image, true, true,
+ ProcessExtendedValue, nullptr, "url(", ")"},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable textColorEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue,
+ nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable borderEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_border, true, false,
+ ProcessExtendedValue, nullptr, nullptr, "px solid"},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable textAlignEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_text_align, true, false,
+ ProcessSameValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable captionAlignEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_caption_side, true, false,
+ ProcessSameValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable verticalAlignEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_vertical_align, true, false,
+ ProcessSameValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable nowrapEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_whitespace, true, false,
+ ProcessDefaultValue, "nowrap", nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable widthEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_width, true, false, ProcessLengthValue,
+ nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable heightEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_height, true, false, ProcessLengthValue,
+ nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable listStyleTypeEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_list_style_type, true, true,
+ ProcessListStyleTypeValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable tableAlignEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_text_align, false, false,
+ ProcessDefaultValue, "left", nullptr, nullptr},
+ {CSSEditUtils::eCSSEditableProperty_margin_left, true, false,
+ ProcessMarginLeftValue, nullptr, nullptr, nullptr},
+ {CSSEditUtils::eCSSEditableProperty_margin_right, true, false,
+ ProcessMarginRightValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+const CSSEditUtils::CSSEquivTable hrAlignEquivTable[] = {
+ {CSSEditUtils::eCSSEditableProperty_margin_left, true, false,
+ ProcessMarginLeftValue, nullptr, nullptr, nullptr},
+ {CSSEditUtils::eCSSEditableProperty_margin_right, true, false,
+ ProcessMarginRightValue, nullptr, nullptr, nullptr},
+ CSS_EQUIV_TABLE_NONE};
+
+#undef CSS_EQUIV_TABLE_NONE
+
+// static
+bool CSSEditUtils::IsCSSEditableStyle(const Element& aElement,
+ const EditorElementStyle& aStyle) {
+ return CSSEditUtils::IsCSSEditableStyle(*aElement.NodeInfo()->NameAtom(),
+ aStyle);
+}
+
+// static
+bool CSSEditUtils::IsCSSEditableStyle(const nsAtom& aTagName,
+ const EditorElementStyle& aStyle) {
+ nsStaticAtom* const htmlProperty =
+ aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mHTMLProperty : nullptr;
+ nsAtom* const attributeOrStyle = aStyle.IsInlineStyle()
+ ? aStyle.AsInlineStyle().mAttribute.get()
+ : aStyle.Style();
+
+ // HTML inline styles <b>, <i>, <tt> (chrome only), <u>, <strike>, <font
+ // color> and <font style>.
+ if (nsGkAtoms::b == htmlProperty || nsGkAtoms::i == htmlProperty ||
+ nsGkAtoms::tt == htmlProperty || nsGkAtoms::u == htmlProperty ||
+ nsGkAtoms::strike == htmlProperty ||
+ (nsGkAtoms::font == htmlProperty &&
+ (attributeOrStyle == nsGkAtoms::color ||
+ attributeOrStyle == nsGkAtoms::face))) {
+ return true;
+ }
+
+ // ALIGN attribute on elements supporting it
+ if (attributeOrStyle == nsGkAtoms::align &&
+ (&aTagName == nsGkAtoms::div || &aTagName == nsGkAtoms::p ||
+ &aTagName == nsGkAtoms::h1 || &aTagName == nsGkAtoms::h2 ||
+ &aTagName == nsGkAtoms::h3 || &aTagName == nsGkAtoms::h4 ||
+ &aTagName == nsGkAtoms::h5 || &aTagName == nsGkAtoms::h6 ||
+ &aTagName == nsGkAtoms::td || &aTagName == nsGkAtoms::th ||
+ &aTagName == nsGkAtoms::table || &aTagName == nsGkAtoms::hr ||
+ // For the above, why not use
+ // HTMLEditUtils::SupportsAlignAttr?
+ // It also checks for tbody, tfoot, thead.
+ // Let's add the following elements here even
+ // if "align" has a different meaning for them
+ &aTagName == nsGkAtoms::legend || &aTagName == nsGkAtoms::caption)) {
+ return true;
+ }
+
+ if (attributeOrStyle == nsGkAtoms::valign &&
+ (&aTagName == nsGkAtoms::col || &aTagName == nsGkAtoms::colgroup ||
+ &aTagName == nsGkAtoms::tbody || &aTagName == nsGkAtoms::td ||
+ &aTagName == nsGkAtoms::th || &aTagName == nsGkAtoms::tfoot ||
+ &aTagName == nsGkAtoms::thead || &aTagName == nsGkAtoms::tr)) {
+ return true;
+ }
+
+ // attributes TEXT, BACKGROUND and BGCOLOR on <body>
+ if (&aTagName == nsGkAtoms::body &&
+ (attributeOrStyle == nsGkAtoms::text ||
+ attributeOrStyle == nsGkAtoms::background ||
+ attributeOrStyle == nsGkAtoms::bgcolor)) {
+ return true;
+ }
+
+ // attribute BGCOLOR on other elements
+ if (attributeOrStyle == nsGkAtoms::bgcolor) {
+ return true;
+ }
+
+ // attributes HEIGHT, WIDTH and NOWRAP on <td> and <th>
+ if ((&aTagName == nsGkAtoms::td || &aTagName == nsGkAtoms::th) &&
+ (attributeOrStyle == nsGkAtoms::height ||
+ attributeOrStyle == nsGkAtoms::width ||
+ attributeOrStyle == nsGkAtoms::nowrap)) {
+ return true;
+ }
+
+ // attributes HEIGHT and WIDTH on <table>
+ if (&aTagName == nsGkAtoms::table && (attributeOrStyle == nsGkAtoms::height ||
+ attributeOrStyle == nsGkAtoms::width)) {
+ return true;
+ }
+
+ // attributes SIZE and WIDTH on <hr>
+ if (&aTagName == nsGkAtoms::hr && (attributeOrStyle == nsGkAtoms::size ||
+ attributeOrStyle == nsGkAtoms::width)) {
+ return true;
+ }
+
+ // attribute TYPE on <ol>, <ul> and <li>
+ if (attributeOrStyle == nsGkAtoms::type &&
+ (&aTagName == nsGkAtoms::ol || &aTagName == nsGkAtoms::ul ||
+ &aTagName == nsGkAtoms::li)) {
+ return true;
+ }
+
+ if (&aTagName == nsGkAtoms::img && (attributeOrStyle == nsGkAtoms::border ||
+ attributeOrStyle == nsGkAtoms::width ||
+ attributeOrStyle == nsGkAtoms::height)) {
+ return true;
+ }
+
+ // other elements that we can align using CSS even if they
+ // can't carry the html ALIGN attribute
+ if (attributeOrStyle == nsGkAtoms::align &&
+ (&aTagName == nsGkAtoms::ul || &aTagName == nsGkAtoms::ol ||
+ &aTagName == nsGkAtoms::dl || &aTagName == nsGkAtoms::li ||
+ &aTagName == nsGkAtoms::dd || &aTagName == nsGkAtoms::dt ||
+ &aTagName == nsGkAtoms::address || &aTagName == nsGkAtoms::pre)) {
+ return true;
+ }
+
+ return false;
+}
+
+// The lowest level above the transaction; adds the CSS declaration
+// "aProperty : aValue" to the inline styles carried by aStyledElement
+
+// static
+nsresult CSSEditUtils::SetCSSPropertyInternal(HTMLEditor& aHTMLEditor,
+ nsStyledElement& aStyledElement,
+ nsAtom& aProperty,
+ const nsAString& aValue,
+ bool aSuppressTxn) {
+ RefPtr<ChangeStyleTransaction> transaction =
+ ChangeStyleTransaction::Create(aStyledElement, aProperty, aValue);
+ if (aSuppressTxn) {
+ nsresult rv = transaction->DoTransaction();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "ChangeStyleTransaction::DoTransaction() failed");
+ return rv;
+ }
+ nsresult rv = aHTMLEditor.DoTransactionInternal(transaction);
+ if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::DoTransactionInternal() failed");
+ return rv;
+}
+
+// static
+nsresult CSSEditUtils::SetCSSPropertyPixelsWithTransaction(
+ HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty,
+ int32_t aIntValue) {
+ nsAutoString s;
+ s.AppendInt(aIntValue);
+ nsresult rv = SetCSSPropertyWithTransaction(aHTMLEditor, aStyledElement,
+ aProperty, s + u"px"_ns);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "CSSEditUtils::SetCSSPropertyWithTransaction() failed");
+ return rv;
+}
+
+// static
+nsresult CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction(
+ nsStyledElement& aStyledElement, const nsAtom& aProperty,
+ int32_t aIntValue) {
+ nsCOMPtr<nsICSSDeclaration> cssDecl = aStyledElement.Style();
+
+ nsAutoCString propertyNameString;
+ aProperty.ToUTF8String(propertyNameString);
+
+ nsAutoCString s;
+ s.AppendInt(aIntValue);
+ s.AppendLiteral("px");
+
+ ErrorResult error;
+ cssDecl->SetProperty(propertyNameString, s, EmptyCString(), error);
+ if (error.Failed()) {
+ NS_WARNING("nsICSSDeclaration::SetProperty() failed");
+ return error.StealNSResult();
+ }
+
+ return NS_OK;
+}
+
+// The lowest level above the transaction; removes the value aValue from the
+// list of values specified for the CSS property aProperty, or totally remove
+// the declaration if this property accepts only one value
+
+// static
+nsresult CSSEditUtils::RemoveCSSPropertyInternal(
+ HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty,
+ const nsAString& aValue, bool aSuppressTxn) {
+ RefPtr<ChangeStyleTransaction> transaction =
+ ChangeStyleTransaction::CreateToRemove(aStyledElement, aProperty, aValue);
+ if (aSuppressTxn) {
+ nsresult rv = transaction->DoTransaction();
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "ChangeStyleTransaction::DoTransaction() failed");
+ return rv;
+ }
+ nsresult rv = aHTMLEditor.DoTransactionInternal(transaction);
+ if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
+ return NS_ERROR_EDITOR_DESTROYED;
+ }
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
+ "EditorBase::DoTransactionInternal() failed");
+ return rv;
+}
+
+// static
+nsresult CSSEditUtils::GetSpecifiedProperty(nsIContent& aContent,
+ nsAtom& aCSSProperty,
+ nsAString& aValue) {
+ nsresult rv =
+ GetSpecifiedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "CSSEditUtils::GeSpecifiedCSSInlinePropertyBase() failed");
+ return rv;
+}
+
+// static
+nsresult CSSEditUtils::GetComputedProperty(nsIContent& aContent,
+ nsAtom& aCSSProperty,
+ nsAString& aValue) {
+ nsresult rv =
+ GetComputedCSSInlinePropertyBase(aContent, aCSSProperty, aValue);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rv),
+ "CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
+ return rv;
+}
+
+// static
+nsresult CSSEditUtils::GetComputedCSSInlinePropertyBase(nsIContent& aContent,
+ nsAtom& aCSSProperty,
+ nsAString& aValue) {
+ aValue.Truncate();
+
+ RefPtr<Element> element = aContent.GetAsElementOrParentElement();
+ if (NS_WARN_IF(!element)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // Get the all the computed css styles attached to the element node
+ RefPtr<nsComputedDOMStyle> computedDOMStyle = GetComputedStyle(element);
+ if (NS_WARN_IF(!computedDOMStyle)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // from these declarations, get the one we want and that one only
+ //
+ // FIXME(bug 1606994): nsAtomCString copies, we should just keep around the
+ // property id.
+ //
+ // FIXME: Maybe we can avoid copying aValue too, though it's no worse than
+ // what we used to do.
+ nsAutoCString value;
+ MOZ_ALWAYS_SUCCEEDS(
+ computedDOMStyle->GetPropertyValue(nsAtomCString(&aCSSProperty), value));
+ CopyUTF8toUTF16(value, aValue);
+ return NS_OK;
+}
+
+// static
+nsresult CSSEditUtils::GetSpecifiedCSSInlinePropertyBase(nsIContent& aContent,
+ nsAtom& aCSSProperty,
+ nsAString& aValue) {
+ aValue.Truncate();
+
+ RefPtr<Element> element = aContent.GetAsElementOrParentElement();
+ if (NS_WARN_IF(!element)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<DeclarationBlock> decl = element->GetInlineStyleDeclaration();
+ if (!decl) {
+ return NS_OK;
+ }
+
+ // FIXME: Same comments as above.
+ nsCSSPropertyID prop =
+ nsCSSProps::LookupProperty(nsAtomCString(&aCSSProperty));
+ MOZ_ASSERT(prop != eCSSProperty_UNKNOWN);
+
+ nsAutoCString value;
+ decl->GetPropertyValueByID(prop, value);
+ CopyUTF8toUTF16(value, aValue);
+ return NS_OK;
+}
+
+// static
+already_AddRefed<nsComputedDOMStyle> CSSEditUtils::GetComputedStyle(
+ Element* aElement) {
+ MOZ_ASSERT(aElement);
+
+ Document* document = aElement->GetComposedDoc();
+ if (NS_WARN_IF(!document)) {
+ return nullptr;
+ }
+
+ RefPtr<nsComputedDOMStyle> computedDOMStyle = NS_NewComputedDOMStyle(
+ aElement, u""_ns, document, nsComputedDOMStyle::StyleType::All,
+ IgnoreErrors());
+ return computedDOMStyle.forget();
+}
+
+// remove the CSS style "aProperty : aPropertyValue" and possibly remove the
+// whole node if it is a span and if its only attribute is _moz_dirty
+
+// static
+Result<EditorDOMPoint, nsresult>
+CSSEditUtils::RemoveCSSInlineStyleWithTransaction(
+ HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom* aProperty,
+ const nsAString& aPropertyValue) {
+ // remove the property from the style attribute
+ nsresult rv = RemoveCSSPropertyWithTransaction(aHTMLEditor, aStyledElement,
+ *aProperty, aPropertyValue);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed");
+ return Err(rv);
+ }
+
+ if (!aStyledElement.IsHTMLElement(nsGkAtoms::span) ||
+ HTMLEditUtils::ElementHasAttribute(aStyledElement)) {
+ return EditorDOMPoint();
+ }
+
+ Result<EditorDOMPoint, nsresult> unwrapStyledElementResult =
+ aHTMLEditor.RemoveContainerWithTransaction(aStyledElement);
+ NS_WARNING_ASSERTION(unwrapStyledElementResult.isOk(),
+ "HTMLEditor::RemoveContainerWithTransaction() failed");
+ return unwrapStyledElementResult;
+}
+
+// Get the default browser background color if we need it for
+// GetCSSBackgroundColorState
+
+// static
+void CSSEditUtils::GetDefaultBackgroundColor(nsAString& aColor) {
+ if (MOZ_UNLIKELY(StaticPrefs::editor_use_custom_colors())) {
+ nsresult rv = Preferences::GetString("editor.background_color", aColor);
+ // XXX Why don't you validate the pref value?
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get editor.background_color");
+ aColor.AssignLiteral("#ffffff"); // Default to white
+ }
+ return;
+ }
+
+ if (Preferences::GetBool("browser.display.use_system_colors", false)) {
+ return;
+ }
+
+ nsresult rv =
+ Preferences::GetString("browser.display.background_color", aColor);
+ // XXX Why don't you validate the pref value?
+ if (NS_FAILED(rv)) {
+ NS_WARNING("failed to get browser.display.background_color");
+ aColor.AssignLiteral("#ffffff"); // Default to white
+ }
+}
+
+// static
+void CSSEditUtils::ParseLength(const nsAString& aString, float* aValue,
+ nsAtom** aUnit) {
+ if (aString.IsEmpty()) {
+ *aValue = 0;
+ *aUnit = NS_Atomize(aString).take();
+ return;
+ }
+
+ nsAString::const_iterator iter;
+ aString.BeginReading(iter);
+
+ float a = 10.0f, b = 1.0f, value = 0;
+ int8_t sign = 1;
+ int32_t i = 0, j = aString.Length();
+ char16_t c;
+ bool floatingPointFound = false;
+ c = *iter;
+ if (char16_t('-') == c) {
+ sign = -1;
+ iter++;
+ i++;
+ } else if (char16_t('+') == c) {
+ iter++;
+ i++;
+ }
+ while (i < j) {
+ c = *iter;
+ if ((char16_t('0') == c) || (char16_t('1') == c) || (char16_t('2') == c) ||
+ (char16_t('3') == c) || (char16_t('4') == c) || (char16_t('5') == c) ||
+ (char16_t('6') == c) || (char16_t('7') == c) || (char16_t('8') == c) ||
+ (char16_t('9') == c)) {
+ value = (value * a) + (b * (c - char16_t('0')));
+ b = b / 10 * a;
+ } else if (!floatingPointFound && (char16_t('.') == c)) {
+ floatingPointFound = true;
+ a = 1.0f;
+ b = 0.1f;
+ } else
+ break;
+ iter++;
+ i++;
+ }
+ *aValue = value * sign;
+ *aUnit = NS_Atomize(StringTail(aString, j - i)).take();
+}
+
+// static
+nsStaticAtom* CSSEditUtils::GetCSSPropertyAtom(
+ nsCSSEditableProperty aProperty) {
+ switch (aProperty) {
+ case eCSSEditableProperty_background_color:
+ return nsGkAtoms::backgroundColor;
+ case eCSSEditableProperty_background_image:
+ return nsGkAtoms::background_image;
+ case eCSSEditableProperty_border:
+ return nsGkAtoms::border;
+ case eCSSEditableProperty_caption_side:
+ return nsGkAtoms::caption_side;
+ case eCSSEditableProperty_color:
+ return nsGkAtoms::color;
+ case eCSSEditableProperty_float:
+ return nsGkAtoms::_float;
+ case eCSSEditableProperty_font_family:
+ return nsGkAtoms::font_family;
+ case eCSSEditableProperty_font_size:
+ return nsGkAtoms::font_size;
+ case eCSSEditableProperty_font_style:
+ return nsGkAtoms::font_style;
+ case eCSSEditableProperty_font_weight:
+ return nsGkAtoms::fontWeight;
+ case eCSSEditableProperty_height:
+ return nsGkAtoms::height;
+ case eCSSEditableProperty_list_style_type:
+ return nsGkAtoms::list_style_type;
+ case eCSSEditableProperty_margin_left:
+ return nsGkAtoms::marginLeft;
+ case eCSSEditableProperty_margin_right:
+ return nsGkAtoms::marginRight;
+ case eCSSEditableProperty_text_align:
+ return nsGkAtoms::textAlign;
+ case eCSSEditableProperty_text_decoration:
+ return nsGkAtoms::text_decoration;
+ case eCSSEditableProperty_vertical_align:
+ return nsGkAtoms::vertical_align;
+ case eCSSEditableProperty_whitespace:
+ return nsGkAtoms::white_space;
+ case eCSSEditableProperty_width:
+ return nsGkAtoms::width;
+ case eCSSEditableProperty_NONE:
+ // intentionally empty
+ return nullptr;
+ }
+ MOZ_ASSERT_UNREACHABLE("Got unknown property");
+ return nullptr;
+}
+
+// static
+void CSSEditUtils::GetCSSDeclarations(
+ const CSSEquivTable* aEquivTable, const nsAString* aValue,
+ HandlingFor aHandlingFor, nsTArray<CSSDeclaration>& aOutCSSDeclarations) {
+ // clear arrays
+ aOutCSSDeclarations.Clear();
+
+ // if we have an input value, let's use it
+ nsAutoString value, lowerCasedValue;
+ if (aValue) {
+ value.Assign(*aValue);
+ lowerCasedValue.Assign(*aValue);
+ ToLowerCase(lowerCasedValue);
+ }
+
+ for (size_t index = 0;; index++) {
+ const nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty;
+ if (!cssProperty) {
+ break;
+ }
+ if (aHandlingFor == HandlingFor::SettingStyle ||
+ aEquivTable[index].gettable) {
+ nsAutoString cssValue, cssPropertyString;
+ // find the equivalent css value for the index-th property in
+ // the equivalence table
+ (*aEquivTable[index].processValueFunctor)(
+ (aHandlingFor == HandlingFor::SettingStyle ||
+ aEquivTable[index].caseSensitiveValue)
+ ? &value
+ : &lowerCasedValue,
+ cssValue, aEquivTable[index].defaultValue,
+ aEquivTable[index].prependValue, aEquivTable[index].appendValue);
+ nsStaticAtom* const propertyAtom = GetCSSPropertyAtom(cssProperty);
+ if (MOZ_LIKELY(propertyAtom)) {
+ aOutCSSDeclarations.AppendElement(
+ CSSDeclaration{*propertyAtom, cssValue});
+ }
+ }
+ }
+}
+
+// static
+void CSSEditUtils::GetCSSDeclarations(
+ Element& aElement, const EditorElementStyle& aStyle,
+ const nsAString* aValue, HandlingFor aHandlingFor,
+ nsTArray<CSSDeclaration>& aOutCSSDeclarations) {
+ nsStaticAtom* const htmlProperty =
+ aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mHTMLProperty : nullptr;
+ const RefPtr<nsAtom> attributeOrStyle =
+ aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mAttribute
+ : aStyle.Style();
+
+ const auto* equivTable = [&]() -> const CSSEditUtils::CSSEquivTable* {
+ if (nsGkAtoms::b == htmlProperty) {
+ return boldEquivTable;
+ }
+ if (nsGkAtoms::i == htmlProperty) {
+ return italicEquivTable;
+ }
+ if (nsGkAtoms::u == htmlProperty) {
+ return underlineEquivTable;
+ }
+ if (nsGkAtoms::strike == htmlProperty) {
+ return strikeEquivTable;
+ }
+ if (nsGkAtoms::tt == htmlProperty) {
+ return ttEquivTable;
+ }
+ if (!attributeOrStyle) {
+ return nullptr;
+ }
+ if (nsGkAtoms::font == htmlProperty) {
+ if (attributeOrStyle == nsGkAtoms::color) {
+ return fontColorEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::face) {
+ return fontFaceEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::size) {
+ return fontSizeEquivTable;
+ }
+ MOZ_ASSERT(attributeOrStyle == nsGkAtoms::bgcolor);
+ }
+ if (attributeOrStyle == nsGkAtoms::bgcolor) {
+ return bgcolorEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::background) {
+ return backgroundImageEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::text) {
+ return textColorEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::border) {
+ return borderEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::align) {
+ if (aElement.IsHTMLElement(nsGkAtoms::table)) {
+ return tableAlignEquivTable;
+ }
+ if (aElement.IsHTMLElement(nsGkAtoms::hr)) {
+ return hrAlignEquivTable;
+ }
+ if (aElement.IsAnyOfHTMLElements(nsGkAtoms::legend, nsGkAtoms::caption)) {
+ return captionAlignEquivTable;
+ }
+ return textAlignEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::valign) {
+ return verticalAlignEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::nowrap) {
+ return nowrapEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::width) {
+ return widthEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::height ||
+ (aElement.IsHTMLElement(nsGkAtoms::hr) &&
+ attributeOrStyle == nsGkAtoms::size)) {
+ return heightEquivTable;
+ }
+ if (attributeOrStyle == nsGkAtoms::type &&
+ aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul,
+ nsGkAtoms::li)) {
+ return listStyleTypeEquivTable;
+ }
+ return nullptr;
+ }();
+ if (equivTable) {
+ GetCSSDeclarations(equivTable, aValue, aHandlingFor, aOutCSSDeclarations);
+ }
+}
+
+// Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/
+// aValue for the node, and return in aCount the number of CSS properties set
+// by the call. The Element version returns aCount instead.
+Result<size_t, nsresult> CSSEditUtils::SetCSSEquivalentToStyle(
+ WithTransaction aWithTransaction, HTMLEditor& aHTMLEditor,
+ nsStyledElement& aStyledElement, const EditorElementStyle& aStyleToSet,
+ const nsAString* aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(aStyleToSet.IsCSSSettable(aStyledElement));
+
+ // we can apply the styles only if the node is an element and if we have
+ // an equivalence for the requested HTML style in this implementation
+
+ // Find the CSS equivalence to the HTML style
+ AutoTArray<CSSDeclaration, 4> cssDeclarations;
+ GetCSSDeclarations(aStyledElement, aStyleToSet, aValue,
+ HandlingFor::SettingStyle, cssDeclarations);
+
+ // set the individual CSS inline styles
+ for (const CSSDeclaration& cssDeclaration : cssDeclarations) {
+ nsresult rv = SetCSSPropertyInternal(
+ aHTMLEditor, aStyledElement, MOZ_KnownLive(cssDeclaration.mProperty),
+ cssDeclaration.mValue, aWithTransaction == WithTransaction::No);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CSSEditUtils::SetCSSPropertyInternal() failed");
+ return Err(rv);
+ }
+ }
+ return cssDeclarations.Length();
+}
+
+// static
+nsresult CSSEditUtils::RemoveCSSEquivalentToStyle(
+ WithTransaction aWithTransaction, HTMLEditor& aHTMLEditor,
+ nsStyledElement& aStyledElement, const EditorElementStyle& aStyleToRemove,
+ const nsAString* aValue) {
+ MOZ_DIAGNOSTIC_ASSERT(aStyleToRemove.IsCSSRemovable(aStyledElement));
+
+ // we can apply the styles only if the node is an element and if we have
+ // an equivalence for the requested HTML style in this implementation
+
+ // Find the CSS equivalence to the HTML style
+ AutoTArray<CSSDeclaration, 4> cssDeclarations;
+ GetCSSDeclarations(aStyledElement, aStyleToRemove, aValue,
+ HandlingFor::RemovingStyle, cssDeclarations);
+
+ // remove the individual CSS inline styles
+ for (const CSSDeclaration& cssDeclaration : cssDeclarations) {
+ nsresult rv = RemoveCSSPropertyInternal(
+ aHTMLEditor, aStyledElement, MOZ_KnownLive(cssDeclaration.mProperty),
+ cssDeclaration.mValue, aWithTransaction == WithTransaction::No);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithoutTransaction() failed");
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+// static
+nsresult CSSEditUtils::GetComputedCSSEquivalentTo(
+ Element& aElement, const EditorElementStyle& aStyle, nsAString& aOutValue) {
+ return GetCSSEquivalentTo(aElement, aStyle, aOutValue, StyleType::Computed);
+}
+
+// static
+nsresult CSSEditUtils::GetCSSEquivalentTo(Element& aElement,
+ const EditorElementStyle& aStyle,
+ nsAString& aOutValue,
+ StyleType aStyleType) {
+ MOZ_ASSERT_IF(aStyle.IsInlineStyle(),
+ !aStyle.AsInlineStyle().IsStyleToClearAllInlineStyles());
+ MOZ_DIAGNOSTIC_ASSERT(aStyle.IsCSSSettable(aElement) ||
+ aStyle.IsCSSRemovable(aElement));
+
+ aOutValue.Truncate();
+ AutoTArray<CSSDeclaration, 4> cssDeclarations;
+ GetCSSDeclarations(aElement, aStyle, nullptr, HandlingFor::GettingStyle,
+ cssDeclarations);
+ nsAutoString valueString;
+ for (const CSSDeclaration& cssDeclaration : cssDeclarations) {
+ valueString.Truncate();
+ // retrieve the specified/computed value of the property
+ if (aStyleType == StyleType::Computed) {
+ nsresult rv = GetComputedCSSInlinePropertyBase(
+ aElement, MOZ_KnownLive(cssDeclaration.mProperty), valueString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CSSEditUtils::GetComputedCSSInlinePropertyBase() failed");
+ return rv;
+ }
+ } else {
+ nsresult rv = GetSpecifiedCSSInlinePropertyBase(
+ aElement, cssDeclaration.mProperty, valueString);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("CSSEditUtils::GetSpecifiedCSSInlinePropertyBase() failed");
+ return rv;
+ }
+ }
+ // append the value to aOutValue (possibly with a leading white-space)
+ if (!aOutValue.IsEmpty()) {
+ aOutValue.Append(HTMLEditUtils::kSpace);
+ }
+ aOutValue.Append(valueString);
+ }
+ return NS_OK;
+}
+
+// Does the node aContent (or its parent, if it's not an element node) have a
+// CSS style equivalent to the HTML style
+// aHTMLProperty/aAttribute/valueString? The value of aStyleType controls
+// the styles we retrieve: specified or computed. The return value aIsSet is
+// true if the CSS styles are set.
+//
+// The nsIContent variant returns aIsSet instead of using an out parameter, and
+// does not modify aValue.
+
+// static
+Result<bool, nsresult> CSSEditUtils::IsComputedCSSEquivalentTo(
+ const HTMLEditor& aHTMLEditor, nsIContent& aContent,
+ const EditorInlineStyle& aStyle, nsAString& aInOutValue) {
+ return IsCSSEquivalentTo(aHTMLEditor, aContent, aStyle, aInOutValue,
+ StyleType::Computed);
+}
+
+// static
+Result<bool, nsresult> CSSEditUtils::IsSpecifiedCSSEquivalentTo(
+ const HTMLEditor& aHTMLEditor, nsIContent& aContent,
+ const EditorInlineStyle& aStyle, nsAString& aInOutValue) {
+ return IsCSSEquivalentTo(aHTMLEditor, aContent, aStyle, aInOutValue,
+ StyleType::Specified);
+}
+
+// static
+Result<bool, nsresult> CSSEditUtils::IsCSSEquivalentTo(
+ const HTMLEditor& aHTMLEditor, nsIContent& aContent,
+ const EditorInlineStyle& aStyle, nsAString& aInOutValue,
+ StyleType aStyleType) {
+ MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles());
+
+ nsAutoString htmlValueString(aInOutValue);
+ bool isSet = false;
+ // FYI: Cannot use InclusiveAncestorsOfType here because
+ // GetCSSEquivalentTo() may flush pending notifications.
+ for (RefPtr<Element> element = aContent.GetAsElementOrParentElement();
+ element; element = element->GetParentElement()) {
+ nsCOMPtr<nsINode> parentNode = element->GetParentNode();
+ aInOutValue.Assign(htmlValueString);
+ // get the value of the CSS equivalent styles
+ nsresult rv = GetCSSEquivalentTo(*element, aStyle, aInOutValue, aStyleType);
+ if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
+ return Err(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
+ "failed");
+ return Err(rv);
+ }
+ if (NS_WARN_IF(parentNode != element->GetParentNode())) {
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ // early way out if we can
+ if (aInOutValue.IsEmpty()) {
+ return isSet;
+ }
+
+ if (nsGkAtoms::b == aStyle.mHTMLProperty) {
+ if (aInOutValue.EqualsLiteral("bold")) {
+ isSet = true;
+ } else if (aInOutValue.EqualsLiteral("normal")) {
+ isSet = false;
+ } else if (aInOutValue.EqualsLiteral("bolder")) {
+ isSet = true;
+ aInOutValue.AssignLiteral("bold");
+ } else {
+ int32_t weight = 0;
+ nsresult rvIgnored;
+ nsAutoString value(aInOutValue);
+ weight = value.ToInteger(&rvIgnored);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
+ "nsAString::ToInteger() failed, but ignored");
+ if (400 < weight) {
+ isSet = true;
+ aInOutValue.AssignLiteral(u"bold");
+ } else {
+ isSet = false;
+ aInOutValue.AssignLiteral(u"normal");
+ }
+ }
+ } else if (nsGkAtoms::i == aStyle.mHTMLProperty) {
+ if (aInOutValue.EqualsLiteral(u"italic") ||
+ aInOutValue.EqualsLiteral(u"oblique")) {
+ isSet = true;
+ }
+ } else if (nsGkAtoms::u == aStyle.mHTMLProperty) {
+ isSet = ChangeStyleTransaction::ValueIncludes(
+ NS_ConvertUTF16toUTF8(aInOutValue), "underline"_ns);
+ } else if (nsGkAtoms::strike == aStyle.mHTMLProperty) {
+ isSet = ChangeStyleTransaction::ValueIncludes(
+ NS_ConvertUTF16toUTF8(aInOutValue), "line-through"_ns);
+ } else if ((nsGkAtoms::font == aStyle.mHTMLProperty &&
+ aStyle.mAttribute == nsGkAtoms::color) ||
+ aStyle.mAttribute == nsGkAtoms::bgcolor) {
+ isSet = htmlValueString.IsEmpty() ||
+ HTMLEditUtils::IsSameCSSColorValue(htmlValueString, aInOutValue);
+ } else if (nsGkAtoms::tt == aStyle.mHTMLProperty) {
+ isSet = StringBeginsWith(aInOutValue, u"monospace"_ns);
+ } else if (nsGkAtoms::font == aStyle.mHTMLProperty &&
+ aStyle.mAttribute == nsGkAtoms::face) {
+ if (!htmlValueString.IsEmpty()) {
+ const char16_t commaSpace[] = {char16_t(','), HTMLEditUtils::kSpace, 0};
+ const char16_t comma[] = {char16_t(','), 0};
+ htmlValueString.ReplaceSubstring(commaSpace, comma);
+ nsAutoString valueStringNorm(aInOutValue);
+ valueStringNorm.ReplaceSubstring(commaSpace, comma);
+ isSet = htmlValueString.Equals(valueStringNorm,
+ nsCaseInsensitiveStringComparator);
+ } else {
+ isSet = true;
+ }
+ return isSet;
+ } else if (aStyle.IsStyleOfFontSize()) {
+ if (htmlValueString.IsEmpty()) {
+ return true;
+ }
+ switch (nsContentUtils::ParseLegacyFontSize(htmlValueString)) {
+ case 1:
+ return aInOutValue.EqualsLiteral("x-small");
+ case 2:
+ return aInOutValue.EqualsLiteral("small");
+ case 3:
+ return aInOutValue.EqualsLiteral("medium");
+ case 4:
+ return aInOutValue.EqualsLiteral("large");
+ case 5:
+ return aInOutValue.EqualsLiteral("x-large");
+ case 6:
+ return aInOutValue.EqualsLiteral("xx-large");
+ case 7:
+ return aInOutValue.EqualsLiteral("xxx-large");
+ }
+ return false;
+ } else if (aStyle.mAttribute == nsGkAtoms::align) {
+ isSet = true;
+ } else {
+ return false;
+ }
+
+ if (!htmlValueString.IsEmpty() &&
+ htmlValueString.Equals(aInOutValue,
+ nsCaseInsensitiveStringComparator)) {
+ isSet = true;
+ }
+
+ if (htmlValueString.EqualsLiteral(u"-moz-editor-invert-value")) {
+ isSet = !isSet;
+ }
+
+ if (isSet) {
+ return true;
+ }
+
+ if (!aStyle.IsStyleOfTextDecoration(
+ EditorInlineStyle::IgnoreSElement::Yes)) {
+ return isSet;
+ }
+
+ // Unfortunately, the value of the text-decoration property is not
+ // inherited. that means that we have to look at ancestors of node to see
+ // if they are underlined.
+ }
+ return isSet;
+}
+
+// static
+Result<bool, nsresult> CSSEditUtils::HaveComputedCSSEquivalentStyles(
+ const HTMLEditor& aHTMLEditor, nsIContent& aContent,
+ const EditorInlineStyle& aStyle) {
+ return HaveCSSEquivalentStyles(aHTMLEditor, aContent, aStyle,
+ StyleType::Computed);
+}
+
+// static
+Result<bool, nsresult> CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(
+ const HTMLEditor& aHTMLEditor, nsIContent& aContent,
+ const EditorInlineStyle& aStyle) {
+ return HaveCSSEquivalentStyles(aHTMLEditor, aContent, aStyle,
+ StyleType::Specified);
+}
+
+// static
+Result<bool, nsresult> CSSEditUtils::HaveCSSEquivalentStyles(
+ const HTMLEditor& aHTMLEditor, nsIContent& aContent,
+ const EditorInlineStyle& aStyle, StyleType aStyleType) {
+ MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles());
+
+ // FYI: Unfortunately, we cannot use InclusiveAncestorsOfType here
+ // because GetCSSEquivalentTo() may flush pending notifications.
+ nsAutoString valueString;
+ for (RefPtr<Element> element = aContent.GetAsElementOrParentElement();
+ element; element = element->GetParentElement()) {
+ nsCOMPtr<nsINode> parentNode = element->GetParentNode();
+ // get the value of the CSS equivalent styles
+ nsresult rv = GetCSSEquivalentTo(*element, aStyle, valueString, aStyleType);
+ if (NS_WARN_IF(aHTMLEditor.Destroyed())) {
+ return Err(NS_ERROR_EDITOR_DESTROYED);
+ }
+ if (NS_FAILED(rv)) {
+ NS_WARNING(
+ "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() "
+ "failed");
+ return Err(rv);
+ }
+ if (NS_WARN_IF(parentNode != element->GetParentNode())) {
+ return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
+ }
+
+ if (!valueString.IsEmpty()) {
+ return true;
+ }
+
+ if (!aStyle.IsStyleOfTextDecoration(
+ EditorInlineStyle::IgnoreSElement::Yes)) {
+ return false;
+ }
+
+ // Unfortunately, the value of the text-decoration property is not
+ // inherited.
+ // that means that we have to look at ancestors of node to see if they
+ // are underlined.
+ }
+
+ return false;
+}
+
+// ElementsSameStyle compares two elements and checks if they have the same
+// specified CSS declarations in the STYLE attribute
+// The answer is always negative if at least one of them carries an ID or a
+// class
+
+// static
+bool CSSEditUtils::DoStyledElementsHaveSameStyle(
+ nsStyledElement& aStyledElement, nsStyledElement& aOtherStyledElement) {
+ if (aStyledElement.HasAttr(kNameSpaceID_None, nsGkAtoms::id) ||
+ aOtherStyledElement.HasAttr(kNameSpaceID_None, nsGkAtoms::id)) {
+ // at least one of the spans carries an ID ; suspect a CSS rule applies to
+ // it and refuse to merge the nodes
+ return false;
+ }
+
+ nsAutoString firstClass, otherClass;
+ bool isElementClassSet =
+ aStyledElement.GetAttr(kNameSpaceID_None, nsGkAtoms::_class, firstClass);
+ bool isOtherElementClassSet = aOtherStyledElement.GetAttr(
+ kNameSpaceID_None, nsGkAtoms::_class, otherClass);
+ if (isElementClassSet && isOtherElementClassSet) {
+ // both spans carry a class, let's compare them
+ if (!firstClass.Equals(otherClass)) {
+ // WARNING : technically, the comparison just above is questionable :
+ // from a pure HTML/CSS point of view class="a b" is NOT the same than
+ // class="b a" because a CSS rule could test the exact value of the class
+ // attribute to be "a b" for instance ; from a user's point of view, a
+ // wysiwyg editor should probably NOT make any difference. CSS people
+ // need to discuss this issue before any modification.
+ return false;
+ }
+ } else if (isElementClassSet || isOtherElementClassSet) {
+ // one span only carries a class, early way out
+ return false;
+ }
+
+ // XXX If `GetPropertyValue()` won't run script, we can stop using
+ // nsCOMPtr here.
+ nsCOMPtr<nsICSSDeclaration> firstCSSDecl = aStyledElement.Style();
+ if (!firstCSSDecl) {
+ NS_WARNING("nsStyledElement::Style() failed");
+ return false;
+ }
+ nsCOMPtr<nsICSSDeclaration> otherCSSDecl = aOtherStyledElement.Style();
+ if (!otherCSSDecl) {
+ NS_WARNING("nsStyledElement::Style() failed");
+ return false;
+ }
+
+ const uint32_t firstLength = firstCSSDecl->Length();
+ const uint32_t otherLength = otherCSSDecl->Length();
+ if (firstLength != otherLength) {
+ // early way out if we can
+ return false;
+ }
+
+ if (!firstLength) {
+ // no inline style !
+ return true;
+ }
+
+ for (uint32_t i = 0; i < firstLength; i++) {
+ nsAutoCString firstValue, otherValue;
+ nsAutoCString propertyNameString;
+ firstCSSDecl->Item(i, propertyNameString);
+ DebugOnly<nsresult> rvIgnored =
+ firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
+ rvIgnored = otherCSSDecl->GetPropertyValue(propertyNameString, otherValue);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
+ // FIXME: We need to handle all properties whose values are color.
+ // However, it's too expensive if we keep using string property names.
+ if (propertyNameString.EqualsLiteral("color") ||
+ propertyNameString.EqualsLiteral("background-color")) {
+ if (!HTMLEditUtils::IsSameCSSColorValue(firstValue, otherValue)) {
+ return false;
+ }
+ } else if (!firstValue.Equals(otherValue)) {
+ return false;
+ }
+ }
+ for (uint32_t i = 0; i < otherLength; i++) {
+ nsAutoCString firstValue, otherValue;
+ nsAutoCString propertyNameString;
+ otherCSSDecl->Item(i, propertyNameString);
+ DebugOnly<nsresult> rvIgnored =
+ otherCSSDecl->GetPropertyValue(propertyNameString, otherValue);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
+ rvIgnored = firstCSSDecl->GetPropertyValue(propertyNameString, firstValue);
+ NS_WARNING_ASSERTION(
+ NS_SUCCEEDED(rvIgnored),
+ "nsICSSDeclaration::GetPropertyValue() failed, but ignored");
+ // FIXME: We need to handle all properties whose values are color.
+ // However, it's too expensive if we keep using string property names.
+ if (propertyNameString.EqualsLiteral("color") ||
+ propertyNameString.EqualsLiteral("background-color")) {
+ if (!HTMLEditUtils::IsSameCSSColorValue(firstValue, otherValue)) {
+ return false;
+ }
+ } else if (!firstValue.Equals(otherValue)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace mozilla