/* -*- 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 "mozilla/CSSEditUtils.h" #include "mozilla/Assertions.h" #include "mozilla/ChangeStyleTransaction.h" #include "mozilla/HTMLEditor.h" #include "mozilla/Preferences.h" #include "mozilla/DeclarationBlockInlines.h" #include "mozilla/css/StyleRule.h" #include "mozilla/dom/Element.h" #include "mozilla/mozalloc.h" #include "nsAString.h" #include "nsCOMPtr.h" #include "nsColor.h" #include "nsComputedDOMStyle.h" #ifdef DEBUG #include "nsDocument.h" #endif #include "nsDebug.h" #include "nsDependentSubstring.h" #include "nsError.h" #include "nsGkAtoms.h" #include "nsIAtom.h" #include "nsIContent.h" #include "nsIDOMCSSStyleDeclaration.h" #include "nsIDOMElement.h" #include "nsIDOMNode.h" #include "nsIDOMWindow.h" #include "nsIDocument.h" #include "nsIEditor.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 "nsSubstringTuple.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(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(aPrependString, aOutputString); } aOutputString.Append(*aInputString); if (aAppendString) { AppendASCIItoUTF16(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"); } } } const CSSEditUtils::CSSEquivTable boldEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_font_weight, ProcessBValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable italicEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_font_style, ProcessDefaultValue, "italic", nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable underlineEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "underline", nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable strikeEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "line-through", nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable ttEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_font_family, ProcessDefaultValue, "monospace", nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable fontColorEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable fontFaceEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_font_family, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable bgcolorEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_background_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable backgroundImageEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_background_image, ProcessExtendedValue, nullptr, "url(", ")", true, true }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable textColorEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable borderEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_border, ProcessExtendedValue, nullptr, nullptr, "px solid", true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable textAlignEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_text_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable captionAlignEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_caption_side, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable verticalAlignEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_vertical_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable nowrapEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_whitespace, ProcessDefaultValue, "nowrap", nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable widthEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_width, ProcessLengthValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable heightEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_height, ProcessLengthValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable listStyleTypeEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_list_style_type, ProcessListStyleTypeValue, nullptr, nullptr, nullptr, true, true }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable tableAlignEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_text_align, ProcessDefaultValue, "left", nullptr, nullptr, false, false }, { CSSEditUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; const CSSEditUtils::CSSEquivTable hrAlignEquivTable[] = { { CSSEditUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false }, { CSSEditUtils::eCSSEditableProperty_NONE, 0 } }; CSSEditUtils::CSSEditUtils(HTMLEditor* aHTMLEditor) : mHTMLEditor(aHTMLEditor) , mIsCSSPrefChecked(true) { // let's retrieve the value of the "CSS editing" pref mIsCSSPrefChecked = Preferences::GetBool("editor.use_css", mIsCSSPrefChecked); } CSSEditUtils::~CSSEditUtils() { } // Answers true if we have some CSS equivalence for the HTML style defined // by aProperty and/or aAttribute for the node aNode bool CSSEditUtils::IsCSSEditableProperty(nsINode* aNode, nsIAtom* aProperty, const nsAString* aAttribute) { nsCOMPtr attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr; return IsCSSEditableProperty(aNode, aProperty, attribute); } bool CSSEditUtils::IsCSSEditableProperty(nsINode* aNode, nsIAtom* aProperty, nsIAtom* aAttribute) { MOZ_ASSERT(aNode); nsINode* node = aNode; // we need an element node here if (node->NodeType() == nsIDOMNode::TEXT_NODE) { node = node->GetParentNode(); NS_ENSURE_TRUE(node, false); } // html inline styles B I TT U STRIKE and COLOR/FACE on FONT if (nsGkAtoms::b == aProperty || nsGkAtoms::i == aProperty || nsGkAtoms::tt == aProperty || nsGkAtoms::u == aProperty || nsGkAtoms::strike == aProperty || (nsGkAtoms::font == aProperty && aAttribute && (aAttribute == nsGkAtoms::color || aAttribute == nsGkAtoms::face))) { return true; } // ALIGN attribute on elements supporting it if (aAttribute == nsGkAtoms::align && node->IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::p, nsGkAtoms::h1, nsGkAtoms::h2, nsGkAtoms::h3, nsGkAtoms::h4, nsGkAtoms::h5, nsGkAtoms::h6, nsGkAtoms::td, nsGkAtoms::th, nsGkAtoms::table, 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 nsGkAtoms::legend, nsGkAtoms::caption)) { return true; } if (aAttribute == nsGkAtoms::valign && node->IsAnyOfHTMLElements(nsGkAtoms::col, nsGkAtoms::colgroup, nsGkAtoms::tbody, nsGkAtoms::td, nsGkAtoms::th, nsGkAtoms::tfoot, nsGkAtoms::thead, nsGkAtoms::tr)) { return true; } // attributes TEXT, BACKGROUND and BGCOLOR on BODY if (node->IsHTMLElement(nsGkAtoms::body) && (aAttribute == nsGkAtoms::text || aAttribute == nsGkAtoms::background || aAttribute == nsGkAtoms::bgcolor)) { return true; } // attribute BGCOLOR on other elements if (aAttribute == nsGkAtoms::bgcolor) { return true; } // attributes HEIGHT, WIDTH and NOWRAP on TD and TH if (node->IsAnyOfHTMLElements(nsGkAtoms::td, nsGkAtoms::th) && (aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::nowrap)) { return true; } // attributes HEIGHT and WIDTH on TABLE if (node->IsHTMLElement(nsGkAtoms::table) && (aAttribute == nsGkAtoms::height || aAttribute == nsGkAtoms::width)) { return true; } // attributes SIZE and WIDTH on HR if (node->IsHTMLElement(nsGkAtoms::hr) && (aAttribute == nsGkAtoms::size || aAttribute == nsGkAtoms::width)) { return true; } // attribute TYPE on OL UL LI if (node->IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul, nsGkAtoms::li) && aAttribute == nsGkAtoms::type) { return true; } if (node->IsHTMLElement(nsGkAtoms::img) && (aAttribute == nsGkAtoms::border || aAttribute == nsGkAtoms::width || aAttribute == nsGkAtoms::height)) { return true; } // other elements that we can align using CSS even if they // can't carry the html ALIGN attribute if (aAttribute == nsGkAtoms::align && node->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol, nsGkAtoms::dl, nsGkAtoms::li, nsGkAtoms::dd, nsGkAtoms::dt, nsGkAtoms::address, nsGkAtoms::pre)) { return true; } return false; } // The lowest level above the transaction; adds the CSS declaration // "aProperty : aValue" to the inline styles carried by aElement nsresult CSSEditUtils::SetCSSProperty(Element& aElement, nsIAtom& aProperty, const nsAString& aValue, bool aSuppressTxn) { RefPtr transaction = CreateCSSPropertyTxn(aElement, aProperty, aValue, ChangeStyleTransaction::eSet); if (aSuppressTxn) { return transaction->DoTransaction(); } return mHTMLEditor->DoTransaction(transaction); } nsresult CSSEditUtils::SetCSSPropertyPixels(Element& aElement, nsIAtom& aProperty, int32_t aIntValue) { nsAutoString s; s.AppendInt(aIntValue); return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px"), /* suppress txn */ false); } // 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 nsresult CSSEditUtils::RemoveCSSProperty(Element& aElement, nsIAtom& aProperty, const nsAString& aValue, bool aSuppressTxn) { RefPtr transaction = CreateCSSPropertyTxn(aElement, aProperty, aValue, ChangeStyleTransaction::eRemove); if (aSuppressTxn) { return transaction->DoTransaction(); } return mHTMLEditor->DoTransaction(transaction); } already_AddRefed CSSEditUtils::CreateCSSPropertyTxn( Element& aElement, nsIAtom& aAttribute, const nsAString& aValue, ChangeStyleTransaction::EChangeType aChangeType) { RefPtr transaction = new ChangeStyleTransaction(aElement, aAttribute, aValue, aChangeType); return transaction.forget(); } nsresult CSSEditUtils::GetSpecifiedProperty(nsINode& aNode, nsIAtom& aProperty, nsAString& aValue) { return GetCSSInlinePropertyBase(&aNode, &aProperty, aValue, eSpecified); } nsresult CSSEditUtils::GetComputedProperty(nsINode& aNode, nsIAtom& aProperty, nsAString& aValue) { return GetCSSInlinePropertyBase(&aNode, &aProperty, aValue, eComputed); } nsresult CSSEditUtils::GetCSSInlinePropertyBase(nsINode* aNode, nsIAtom* aProperty, nsAString& aValue, StyleType aStyleType) { MOZ_ASSERT(aNode && aProperty); aValue.Truncate(); nsCOMPtr element = GetElementContainerOrSelf(aNode); NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER); if (aStyleType == eComputed) { // Get the all the computed css styles attached to the element node RefPtr cssDecl = GetComputedStyle(element); NS_ENSURE_STATE(cssDecl); // from these declarations, get the one we want and that one only MOZ_ALWAYS_SUCCEEDS( cssDecl->GetPropertyValue(nsDependentAtomString(aProperty), aValue)); return NS_OK; } MOZ_ASSERT(aStyleType == eSpecified); RefPtr decl = element->GetInlineStyleDeclaration(); if (!decl) { return NS_OK; } if (decl->IsServo()) { MOZ_CRASH("stylo: not implemented"); return NS_ERROR_NOT_IMPLEMENTED; } nsCSSPropertyID prop = nsCSSProps::LookupProperty(nsDependentAtomString(aProperty), CSSEnabledState::eForAllContent); MOZ_ASSERT(prop != eCSSProperty_UNKNOWN); decl->AsGecko()->GetPropertyValueByID(prop, aValue); return NS_OK; } already_AddRefed CSSEditUtils::GetComputedStyle(Element* aElement) { MOZ_ASSERT(aElement); nsIDocument* doc = aElement->GetUncomposedDoc(); NS_ENSURE_TRUE(doc, nullptr); RefPtr style = NS_NewComputedDOMStyle(aElement, EmptyString(), doc); return style.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 nsresult CSSEditUtils::RemoveCSSInlineStyle(nsIDOMNode* aNode, nsIAtom* aProperty, const nsAString& aPropertyValue) { nsCOMPtr element = do_QueryInterface(aNode); NS_ENSURE_STATE(element); // remove the property from the style attribute nsresult rv = RemoveCSSProperty(*element, *aProperty, aPropertyValue); NS_ENSURE_SUCCESS(rv, rv); if (!element->IsHTMLElement(nsGkAtoms::span) || HTMLEditor::HasAttributes(element)) { return NS_OK; } return mHTMLEditor->RemoveContainer(element); } // Answers true if the property can be removed by setting a "none" CSS value // on a node bool CSSEditUtils::IsCSSInvertible(nsIAtom& aProperty, const nsAString* aAttribute) { return nsGkAtoms::b == &aProperty; } // Get the default browser background color if we need it for GetCSSBackgroundColorState void CSSEditUtils::GetDefaultBackgroundColor(nsAString& aColor) { if (Preferences::GetBool("editor.use_custom_colors", false)) { 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 } } // Get the default length unit used for CSS Indent/Outdent void CSSEditUtils::GetDefaultLengthUnit(nsAString& aLengthUnit) { nsresult rv = Preferences::GetString("editor.css.default_length_unit", &aLengthUnit); // XXX Why don't you validate the pref value? if (NS_FAILED(rv)) { aLengthUnit.AssignLiteral("px"); } } // Unfortunately, CSSStyleDeclaration::GetPropertyCSSValue is not yet // implemented... We need then a way to determine the number part and the unit // from aString, aString being the result of a GetPropertyValue query... void CSSEditUtils::ParseLength(const nsAString& aString, float* aValue, nsIAtom** 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(); } void CSSEditUtils::GetCSSPropertyAtom(nsCSSEditableProperty aProperty, nsIAtom** aAtom) { *aAtom = nullptr; switch (aProperty) { case eCSSEditableProperty_background_color: *aAtom = nsGkAtoms::backgroundColor; break; case eCSSEditableProperty_background_image: *aAtom = nsGkAtoms::background_image; break; case eCSSEditableProperty_border: *aAtom = nsGkAtoms::border; break; case eCSSEditableProperty_caption_side: *aAtom = nsGkAtoms::caption_side; break; case eCSSEditableProperty_color: *aAtom = nsGkAtoms::color; break; case eCSSEditableProperty_float: *aAtom = nsGkAtoms::_float; break; case eCSSEditableProperty_font_family: *aAtom = nsGkAtoms::font_family; break; case eCSSEditableProperty_font_size: *aAtom = nsGkAtoms::font_size; break; case eCSSEditableProperty_font_style: *aAtom = nsGkAtoms::font_style; break; case eCSSEditableProperty_font_weight: *aAtom = nsGkAtoms::fontWeight; break; case eCSSEditableProperty_height: *aAtom = nsGkAtoms::height; break; case eCSSEditableProperty_list_style_type: *aAtom = nsGkAtoms::list_style_type; break; case eCSSEditableProperty_margin_left: *aAtom = nsGkAtoms::marginLeft; break; case eCSSEditableProperty_margin_right: *aAtom = nsGkAtoms::marginRight; break; case eCSSEditableProperty_text_align: *aAtom = nsGkAtoms::textAlign; break; case eCSSEditableProperty_text_decoration: *aAtom = nsGkAtoms::text_decoration; break; case eCSSEditableProperty_vertical_align: *aAtom = nsGkAtoms::vertical_align; break; case eCSSEditableProperty_whitespace: *aAtom = nsGkAtoms::white_space; break; case eCSSEditableProperty_width: *aAtom = nsGkAtoms::width; break; case eCSSEditableProperty_NONE: // intentionally empty break; } } // Populate aProperty and aValueArray with the CSS declarations equivalent to the // value aValue according to the equivalence table aEquivTable void CSSEditUtils::BuildCSSDeclarations(nsTArray& aPropertyArray, nsTArray& aValueArray, const CSSEquivTable* aEquivTable, const nsAString* aValue, bool aGetOrRemoveRequest) { // clear arrays aPropertyArray.Clear(); aValueArray.Clear(); // if we have an input value, let's use it nsAutoString value, lowerCasedValue; if (aValue) { value.Assign(*aValue); lowerCasedValue.Assign(*aValue); ToLowerCase(lowerCasedValue); } int8_t index = 0; nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty; while (cssProperty) { if (!aGetOrRemoveRequest|| aEquivTable[index].gettable) { nsAutoString cssValue, cssPropertyString; nsIAtom * cssPropertyAtom; // find the equivalent css value for the index-th property in // the equivalence table (*aEquivTable[index].processValueFunctor) ((!aGetOrRemoveRequest || aEquivTable[index].caseSensitiveValue) ? &value : &lowerCasedValue, cssValue, aEquivTable[index].defaultValue, aEquivTable[index].prependValue, aEquivTable[index].appendValue); GetCSSPropertyAtom(cssProperty, &cssPropertyAtom); aPropertyArray.AppendElement(cssPropertyAtom); aValueArray.AppendElement(cssValue); } index++; cssProperty = aEquivTable[index].cssProperty; } } // Populate cssPropertyArray and cssValueArray with the declarations equivalent // to aHTMLProperty/aAttribute/aValue for the node aNode void CSSEditUtils::GenerateCSSDeclarationsFromHTMLStyle( Element* aElement, nsIAtom* aHTMLProperty, nsIAtom* aAttribute, const nsAString* aValue, nsTArray& cssPropertyArray, nsTArray& cssValueArray, bool aGetOrRemoveRequest) { MOZ_ASSERT(aElement); const CSSEditUtils::CSSEquivTable* equivTable = nullptr; if (nsGkAtoms::b == aHTMLProperty) { equivTable = boldEquivTable; } else if (nsGkAtoms::i == aHTMLProperty) { equivTable = italicEquivTable; } else if (nsGkAtoms::u == aHTMLProperty) { equivTable = underlineEquivTable; } else if (nsGkAtoms::strike == aHTMLProperty) { equivTable = strikeEquivTable; } else if (nsGkAtoms::tt == aHTMLProperty) { equivTable = ttEquivTable; } else if (aAttribute) { if (nsGkAtoms::font == aHTMLProperty && aAttribute == nsGkAtoms::color) { equivTable = fontColorEquivTable; } else if (nsGkAtoms::font == aHTMLProperty && aAttribute == nsGkAtoms::face) { equivTable = fontFaceEquivTable; } else if (aAttribute == nsGkAtoms::bgcolor) { equivTable = bgcolorEquivTable; } else if (aAttribute == nsGkAtoms::background) { equivTable = backgroundImageEquivTable; } else if (aAttribute == nsGkAtoms::text) { equivTable = textColorEquivTable; } else if (aAttribute == nsGkAtoms::border) { equivTable = borderEquivTable; } else if (aAttribute == nsGkAtoms::align) { if (aElement->IsHTMLElement(nsGkAtoms::table)) { equivTable = tableAlignEquivTable; } else if (aElement->IsHTMLElement(nsGkAtoms::hr)) { equivTable = hrAlignEquivTable; } else if (aElement->IsAnyOfHTMLElements(nsGkAtoms::legend, nsGkAtoms::caption)) { equivTable = captionAlignEquivTable; } else { equivTable = textAlignEquivTable; } } else if (aAttribute == nsGkAtoms::valign) { equivTable = verticalAlignEquivTable; } else if (aAttribute == nsGkAtoms::nowrap) { equivTable = nowrapEquivTable; } else if (aAttribute == nsGkAtoms::width) { equivTable = widthEquivTable; } else if (aAttribute == nsGkAtoms::height || (aElement->IsHTMLElement(nsGkAtoms::hr) && aAttribute == nsGkAtoms::size)) { equivTable = heightEquivTable; } else if (aAttribute == nsGkAtoms::type && aElement->IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul, nsGkAtoms::li)) { equivTable = listStyleTypeEquivTable; } } if (equivTable) { BuildCSSDeclarations(cssPropertyArray, cssValueArray, equivTable, aValue, aGetOrRemoveRequest); } } // 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. int32_t CSSEditUtils::SetCSSEquivalentToHTMLStyle(nsIDOMNode* aNode, nsIAtom* aProperty, const nsAString* aAttribute, const nsAString* aValue, bool aSuppressTransaction) { MOZ_ASSERT_IF(aAttribute, aValue); // This can only fail if SetCSSProperty fails, which should only happen if // something is pretty badly wrong. In this case we assert so that hopefully // someone will notice, but there's nothing more sensible to do than just // return the count and carry on. nsCOMPtr element = do_QueryInterface(aNode); return SetCSSEquivalentToHTMLStyle(element, aProperty, aAttribute, aValue, aSuppressTransaction); } int32_t CSSEditUtils::SetCSSEquivalentToHTMLStyle(Element* aElement, nsIAtom* aHTMLProperty, const nsAString* aAttribute, const nsAString* aValue, bool aSuppressTransaction) { nsCOMPtr attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr; return SetCSSEquivalentToHTMLStyle(aElement, aHTMLProperty, attribute, aValue, aSuppressTransaction); } int32_t CSSEditUtils::SetCSSEquivalentToHTMLStyle(Element* aElement, nsIAtom* aHTMLProperty, nsIAtom* aAttribute, const nsAString* aValue, bool aSuppressTransaction) { MOZ_ASSERT(aElement); if (!IsCSSEditableProperty(aElement, aHTMLProperty, aAttribute)) { return 0; } // 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 nsTArray cssPropertyArray; nsTArray cssValueArray; GenerateCSSDeclarationsFromHTMLStyle(aElement, aHTMLProperty, aAttribute, aValue, cssPropertyArray, cssValueArray, false); // set the individual CSS inline styles size_t count = cssPropertyArray.Length(); for (size_t index = 0; index < count; index++) { nsresult rv = SetCSSProperty(*aElement, *cssPropertyArray[index], cssValueArray[index], aSuppressTransaction); if (NS_WARN_IF(NS_FAILED(rv))) { return 0; } } return count; } // Remove from aNode the CSS inline style equivalent to HTMLProperty/aAttribute/aValue for the node nsresult CSSEditUtils::RemoveCSSEquivalentToHTMLStyle(nsIDOMNode* aNode, nsIAtom* aHTMLProperty, const nsAString* aAttribute, const nsAString* aValue, bool aSuppressTransaction) { nsCOMPtr element = do_QueryInterface(aNode); nsCOMPtr attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr; return RemoveCSSEquivalentToHTMLStyle(element, aHTMLProperty, attribute, aValue, aSuppressTransaction); } nsresult CSSEditUtils::RemoveCSSEquivalentToHTMLStyle(Element* aElement, nsIAtom* aHTMLProperty, nsIAtom* aAttribute, const nsAString* aValue, bool aSuppressTransaction) { if (NS_WARN_IF(!aElement)) { return NS_OK; } if (!IsCSSEditableProperty(aElement, aHTMLProperty, aAttribute)) { return NS_OK; } // 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 nsTArray cssPropertyArray; nsTArray cssValueArray; GenerateCSSDeclarationsFromHTMLStyle(aElement, aHTMLProperty, aAttribute, aValue, cssPropertyArray, cssValueArray, true); // remove the individual CSS inline styles int32_t count = cssPropertyArray.Length(); for (int32_t index = 0; index < count; index++) { nsresult rv = RemoveCSSProperty(*aElement, *cssPropertyArray[index], cssValueArray[index], aSuppressTransaction); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } // returns in aValueString the list of values for the CSS equivalences to // the HTML style aHTMLProperty/aAttribute/aValueString for the node aNode; // the value of aStyleType controls the styles we retrieve : specified or // computed. nsresult CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode, nsIAtom* aHTMLProperty, nsIAtom* aAttribute, nsAString& aValueString, StyleType aStyleType) { aValueString.Truncate(); nsCOMPtr theElement = GetElementContainerOrSelf(aNode); NS_ENSURE_TRUE(theElement, NS_ERROR_NULL_POINTER); if (!theElement || !IsCSSEditableProperty(theElement, aHTMLProperty, aAttribute)) { return NS_OK; } // Yes, the requested HTML style has a CSS equivalence in this implementation nsTArray cssPropertyArray; nsTArray cssValueArray; // get the CSS equivalence with last param true indicating we want only the // "gettable" properties GenerateCSSDeclarationsFromHTMLStyle(theElement, aHTMLProperty, aAttribute, nullptr, cssPropertyArray, cssValueArray, true); int32_t count = cssPropertyArray.Length(); for (int32_t index = 0; index < count; index++) { nsAutoString valueString; // retrieve the specified/computed value of the property nsresult rv = GetCSSInlinePropertyBase(theElement, cssPropertyArray[index], valueString, aStyleType); NS_ENSURE_SUCCESS(rv, rv); // append the value to aValueString (possibly with a leading whitespace) if (index) { aValueString.Append(char16_t(' ')); } aValueString.Append(valueString); } return NS_OK; } // Does the node aNode (or its parent, if it's not an element node) have a CSS // style equivalent to the HTML style aHTMLProperty/aHTMLAttribute/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. bool CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode, nsIAtom* aProperty, const nsAString* aAttribute, const nsAString& aValue, StyleType aStyleType) { // Use aValue as only an in param, not in-out nsAutoString value(aValue); return IsCSSEquivalentToHTMLInlineStyleSet(aNode, aProperty, aAttribute, value, aStyleType); } bool CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode, nsIAtom* aProperty, const nsAString* aAttribute, nsAString& aValue, StyleType aStyleType) { MOZ_ASSERT(aNode && aProperty); nsCOMPtr attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr; return IsCSSEquivalentToHTMLInlineStyleSet(aNode, aProperty, attribute, aValue, aStyleType); } bool CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsIDOMNode* aNode, nsIAtom* aProperty, const nsAString* aAttribute, nsAString& aValue, StyleType aStyleType) { MOZ_ASSERT(aNode && aProperty); nsCOMPtr node = do_QueryInterface(aNode); nsCOMPtr attribute = aAttribute ? NS_Atomize(*aAttribute) : nullptr; return IsCSSEquivalentToHTMLInlineStyleSet(node, aProperty, attribute, aValue, aStyleType); } bool CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet( nsINode* aNode, nsIAtom* aHTMLProperty, nsIAtom* aHTMLAttribute, nsAString& valueString, StyleType aStyleType) { NS_ENSURE_TRUE(aNode, false); nsAutoString htmlValueString(valueString); bool isSet = false; do { valueString.Assign(htmlValueString); // get the value of the CSS equivalent styles nsresult rv = GetCSSEquivalentToHTMLInlineStyleSet(aNode, aHTMLProperty, aHTMLAttribute, valueString, aStyleType); NS_ENSURE_SUCCESS(rv, false); // early way out if we can if (valueString.IsEmpty()) { return isSet; } if (nsGkAtoms::b == aHTMLProperty) { if (valueString.EqualsLiteral("bold")) { isSet = true; } else if (valueString.EqualsLiteral("normal")) { isSet = false; } else if (valueString.EqualsLiteral("bolder")) { isSet = true; valueString.AssignLiteral("bold"); } else { int32_t weight = 0; nsresult errorCode; nsAutoString value(valueString); weight = value.ToInteger(&errorCode); if (400 < weight) { isSet = true; valueString.AssignLiteral("bold"); } else { isSet = false; valueString.AssignLiteral("normal"); } } } else if (nsGkAtoms::i == aHTMLProperty) { if (valueString.EqualsLiteral("italic") || valueString.EqualsLiteral("oblique")) { isSet = true; } } else if (nsGkAtoms::u == aHTMLProperty) { nsAutoString val; val.AssignLiteral("underline"); isSet = ChangeStyleTransaction::ValueIncludes(valueString, val); } else if (nsGkAtoms::strike == aHTMLProperty) { nsAutoString val; val.AssignLiteral("line-through"); isSet = ChangeStyleTransaction::ValueIncludes(valueString, val); } else if ((nsGkAtoms::font == aHTMLProperty && aHTMLAttribute == nsGkAtoms::color) || aHTMLAttribute == nsGkAtoms::bgcolor) { if (htmlValueString.IsEmpty()) { isSet = true; } else { nscolor rgba; nsAutoString subStr; htmlValueString.Right(subStr, htmlValueString.Length() - 1); if (NS_ColorNameToRGB(htmlValueString, &rgba) || NS_HexToRGBA(subStr, nsHexColorType::NoAlpha, &rgba)) { nsAutoString htmlColor, tmpStr; if (NS_GET_A(rgba) != 255) { // This should only be hit by the "transparent" keyword, which // currently serializes to "transparent" (not "rgba(0, 0, 0, 0)"). MOZ_ASSERT(NS_GET_R(rgba) == 0 && NS_GET_G(rgba) == 0 && NS_GET_B(rgba) == 0 && NS_GET_A(rgba) == 0); htmlColor.AppendLiteral("transparent"); } else { htmlColor.AppendLiteral("rgb("); NS_NAMED_LITERAL_STRING(comma, ", "); tmpStr.AppendInt(NS_GET_R(rgba), 10); htmlColor.Append(tmpStr + comma); tmpStr.Truncate(); tmpStr.AppendInt(NS_GET_G(rgba), 10); htmlColor.Append(tmpStr + comma); tmpStr.Truncate(); tmpStr.AppendInt(NS_GET_B(rgba), 10); htmlColor.Append(tmpStr); htmlColor.Append(char16_t(')')); } isSet = htmlColor.Equals(valueString, nsCaseInsensitiveStringComparator()); } else { isSet = htmlValueString.Equals(valueString, nsCaseInsensitiveStringComparator()); } } } else if (nsGkAtoms::tt == aHTMLProperty) { isSet = StringBeginsWith(valueString, NS_LITERAL_STRING("monospace")); } else if (nsGkAtoms::font == aHTMLProperty && aHTMLAttribute && aHTMLAttribute == nsGkAtoms::face) { if (!htmlValueString.IsEmpty()) { const char16_t commaSpace[] = { char16_t(','), char16_t(' '), 0 }; const char16_t comma[] = { char16_t(','), 0 }; htmlValueString.ReplaceSubstring(commaSpace, comma); nsAutoString valueStringNorm(valueString); valueStringNorm.ReplaceSubstring(commaSpace, comma); isSet = htmlValueString.Equals(valueStringNorm, nsCaseInsensitiveStringComparator()); } else { isSet = true; } return isSet; } else if (aHTMLAttribute == nsGkAtoms::align) { isSet = true; } else { return false; } if (!htmlValueString.IsEmpty() && htmlValueString.Equals(valueString, nsCaseInsensitiveStringComparator())) { isSet = true; } if (htmlValueString.EqualsLiteral("-moz-editor-invert-value")) { isSet = !isSet; } if (nsGkAtoms::u == aHTMLProperty || nsGkAtoms::strike == aHTMLProperty) { // 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 aNode = aNode->GetParentElement(); // set to null if it's not a dom element } } while ((nsGkAtoms::u == aHTMLProperty || nsGkAtoms::strike == aHTMLProperty) && !isSet && aNode); return isSet; } void CSSEditUtils::SetCSSEnabled(bool aIsCSSPrefChecked) { mIsCSSPrefChecked = aIsCSSPrefChecked; } bool CSSEditUtils::IsCSSPrefChecked() { return mIsCSSPrefChecked ; } // 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 bool CSSEditUtils::ElementsSameStyle(nsIDOMNode* aFirstNode, nsIDOMNode* aSecondNode) { nsCOMPtr firstElement = do_QueryInterface(aFirstNode); nsCOMPtr secondElement = do_QueryInterface(aSecondNode); NS_ASSERTION((firstElement && secondElement), "Non element nodes passed to ElementsSameStyle."); NS_ENSURE_TRUE(firstElement, false); NS_ENSURE_TRUE(secondElement, false); return ElementsSameStyle(firstElement, secondElement); } bool CSSEditUtils::ElementsSameStyle(Element* aFirstElement, Element* aSecondElement) { MOZ_ASSERT(aFirstElement); MOZ_ASSERT(aSecondElement); if (aFirstElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id) || aSecondElement->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, secondClass; bool isFirstClassSet = aFirstElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, firstClass); bool isSecondClassSet = aSecondElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, secondClass); if (isFirstClassSet && isSecondClassSet) { // both spans carry a class, let's compare them if (!firstClass.Equals(secondClass)) { // 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 (isFirstClassSet || isSecondClassSet) { // one span only carries a class, early way out return false; } nsCOMPtr firstCSSDecl, secondCSSDecl; uint32_t firstLength, secondLength; nsresult rv = GetInlineStyles(aFirstElement, getter_AddRefs(firstCSSDecl), &firstLength); if (NS_FAILED(rv) || !firstCSSDecl) { return false; } rv = GetInlineStyles(aSecondElement, getter_AddRefs(secondCSSDecl), &secondLength); if (NS_FAILED(rv) || !secondCSSDecl) { return false; } if (firstLength != secondLength) { // early way out if we can return false; } if (!firstLength) { // no inline style ! return true; } nsAutoString propertyNameString; nsAutoString firstValue, secondValue; for (uint32_t i = 0; i < firstLength; i++) { firstCSSDecl->Item(i, propertyNameString); firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); secondCSSDecl->GetPropertyValue(propertyNameString, secondValue); if (!firstValue.Equals(secondValue)) { return false; } } for (uint32_t i = 0; i < secondLength; i++) { secondCSSDecl->Item(i, propertyNameString); secondCSSDecl->GetPropertyValue(propertyNameString, secondValue); firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); if (!firstValue.Equals(secondValue)) { return false; } } return true; } nsresult CSSEditUtils::GetInlineStyles(Element* aElement, nsIDOMCSSStyleDeclaration** aCssDecl, uint32_t* aLength) { return GetInlineStyles(static_cast(aElement), aCssDecl, aLength); } nsresult CSSEditUtils::GetInlineStyles(nsIDOMElement* aElement, nsIDOMCSSStyleDeclaration** aCssDecl, uint32_t* aLength) { return GetInlineStyles(static_cast(aElement), aCssDecl, aLength); } nsresult CSSEditUtils::GetInlineStyles(nsISupports* aElement, nsIDOMCSSStyleDeclaration** aCssDecl, uint32_t* aLength) { NS_ENSURE_TRUE(aElement && aLength, NS_ERROR_NULL_POINTER); *aLength = 0; nsCOMPtr inlineStyles = do_QueryInterface(aElement); NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); nsCOMPtr cssDecl = do_QueryInterface(inlineStyles->Style()); MOZ_ASSERT(cssDecl); cssDecl.forget(aCssDecl); (*aCssDecl)->GetLength(aLength); return NS_OK; } already_AddRefed CSSEditUtils::GetElementContainerOrSelf(nsIDOMNode* aNode) { nsCOMPtr node = do_QueryInterface(aNode); NS_ENSURE_TRUE(node, nullptr); nsCOMPtr element = do_QueryInterface(GetElementContainerOrSelf(node)); return element.forget(); } Element* CSSEditUtils::GetElementContainerOrSelf(nsINode* aNode) { MOZ_ASSERT(aNode); if (nsIDOMNode::DOCUMENT_NODE == aNode->NodeType()) { return nullptr; } nsINode* node = aNode; // Loop until we find an element. while (node && !node->IsElement()) { node = node->GetParentNode(); } NS_ENSURE_TRUE(node, nullptr); return node->AsElement(); } nsresult CSSEditUtils::SetCSSProperty(nsIDOMElement* aElement, const nsAString& aProperty, const nsAString& aValue) { nsCOMPtr cssDecl; uint32_t length; nsresult rv = GetInlineStyles(aElement, getter_AddRefs(cssDecl), &length); if (NS_FAILED(rv) || !cssDecl) { return rv; } return cssDecl->SetProperty(aProperty, aValue, EmptyString()); } nsresult CSSEditUtils::SetCSSPropertyPixels(nsIDOMElement* aElement, const nsAString& aProperty, int32_t aIntValue) { nsAutoString s; s.AppendInt(aIntValue); return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px")); } } // namespace mozilla