michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "ChangeCSSInlineStyleTxn.h" michael@0: #include "EditTxn.h" michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/css/Declaration.h" michael@0: #include "mozilla/css/StyleRule.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/mozalloc.h" michael@0: #include "nsAString.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsColor.h" michael@0: #include "nsComputedDOMStyle.h" michael@0: #include "nsDebug.h" michael@0: #include "nsDependentSubstring.h" michael@0: #include "nsEditProperty.h" michael@0: #include "nsError.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsHTMLCSSUtils.h" michael@0: #include "nsHTMLEditor.h" michael@0: #include "nsIAtom.h" michael@0: #include "nsIContent.h" michael@0: #include "nsIDOMCSSStyleDeclaration.h" michael@0: #include "nsIDOMElement.h" michael@0: #include "nsIDOMElementCSSInlineStyle.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsINode.h" michael@0: #include "nsISupportsImpl.h" michael@0: #include "nsISupportsUtils.h" michael@0: #include "nsLiteralString.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsReadableUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsStringFwd.h" michael@0: #include "nsStringIterator.h" michael@0: #include "nsSubstringTuple.h" michael@0: #include "nsUnicharUtils.h" michael@0: michael@0: using namespace mozilla; michael@0: michael@0: static michael@0: void ProcessBValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: if (aInputString && aInputString->EqualsLiteral("-moz-editor-invert-value")) { michael@0: aOutputString.AssignLiteral("normal"); michael@0: } michael@0: else { michael@0: aOutputString.AssignLiteral("bold"); michael@0: } michael@0: } michael@0: michael@0: static michael@0: void ProcessDefaultValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: CopyASCIItoUTF16(aDefaultValueString, aOutputString); michael@0: } michael@0: michael@0: static michael@0: void ProcessSameValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: if (aInputString) { michael@0: aOutputString.Assign(*aInputString); michael@0: } michael@0: else michael@0: aOutputString.Truncate(); michael@0: } michael@0: michael@0: static michael@0: void ProcessExtendedValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: aOutputString.Truncate(); michael@0: if (aInputString) { michael@0: if (aPrependString) { michael@0: AppendASCIItoUTF16(aPrependString, aOutputString); michael@0: } michael@0: aOutputString.Append(*aInputString); michael@0: if (aAppendString) { michael@0: AppendASCIItoUTF16(aAppendString, aOutputString); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static michael@0: void ProcessLengthValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: aOutputString.Truncate(); michael@0: if (aInputString) { michael@0: aOutputString.Append(*aInputString); michael@0: if (-1 == aOutputString.FindChar(char16_t('%'))) { michael@0: aOutputString.AppendLiteral("px"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static michael@0: void ProcessListStyleTypeValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: aOutputString.Truncate(); michael@0: if (aInputString) { michael@0: if (aInputString->EqualsLiteral("1")) { michael@0: aOutputString.AppendLiteral("decimal"); michael@0: } michael@0: else if (aInputString->EqualsLiteral("a")) { michael@0: aOutputString.AppendLiteral("lower-alpha"); michael@0: } michael@0: else if (aInputString->EqualsLiteral("A")) { michael@0: aOutputString.AppendLiteral("upper-alpha"); michael@0: } michael@0: else if (aInputString->EqualsLiteral("i")) { michael@0: aOutputString.AppendLiteral("lower-roman"); michael@0: } michael@0: else if (aInputString->EqualsLiteral("I")) { michael@0: aOutputString.AppendLiteral("upper-roman"); michael@0: } michael@0: else if (aInputString->EqualsLiteral("square") michael@0: || aInputString->EqualsLiteral("circle") michael@0: || aInputString->EqualsLiteral("disc")) { michael@0: aOutputString.Append(*aInputString); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static michael@0: void ProcessMarginLeftValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: aOutputString.Truncate(); michael@0: if (aInputString) { michael@0: if (aInputString->EqualsLiteral("center") || michael@0: aInputString->EqualsLiteral("-moz-center")) { michael@0: aOutputString.AppendLiteral("auto"); michael@0: } michael@0: else if (aInputString->EqualsLiteral("right") || michael@0: aInputString->EqualsLiteral("-moz-right")) { michael@0: aOutputString.AppendLiteral("auto"); michael@0: } michael@0: else { michael@0: aOutputString.AppendLiteral("0px"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: static michael@0: void ProcessMarginRightValue(const nsAString * aInputString, nsAString & aOutputString, michael@0: const char * aDefaultValueString, michael@0: const char * aPrependString, const char* aAppendString) michael@0: { michael@0: aOutputString.Truncate(); michael@0: if (aInputString) { michael@0: if (aInputString->EqualsLiteral("center") || michael@0: aInputString->EqualsLiteral("-moz-center")) { michael@0: aOutputString.AppendLiteral("auto"); michael@0: } michael@0: else if (aInputString->EqualsLiteral("left") || michael@0: aInputString->EqualsLiteral("-moz-left")) { michael@0: aOutputString.AppendLiteral("auto"); michael@0: } michael@0: else { michael@0: aOutputString.AppendLiteral("0px"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable boldEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_font_weight, ProcessBValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable italicEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_font_style, ProcessDefaultValue, "italic", nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable underlineEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "underline", nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable strikeEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "line-through", nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable ttEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_font_family, ProcessDefaultValue, "monospace", nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable fontColorEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable fontFaceEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_font_family, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable bgcolorEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_background_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable backgroundImageEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_background_image, ProcessExtendedValue, nullptr, "url(", ")", true, true }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable textColorEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable borderEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_border, ProcessExtendedValue, nullptr, nullptr, "px solid", true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable textAlignEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_text_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable captionAlignEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_caption_side, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable verticalAlignEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_vertical_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable nowrapEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_whitespace, ProcessDefaultValue, "nowrap", nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable widthEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_width, ProcessLengthValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable heightEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_height, ProcessLengthValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable listStyleTypeEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_list_style_type, ProcessListStyleTypeValue, nullptr, nullptr, nullptr, true, true }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable tableAlignEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_text_align, ProcessDefaultValue, "left", nullptr, nullptr, false, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: const nsHTMLCSSUtils::CSSEquivTable hrAlignEquivTable[] = { michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false }, michael@0: { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } michael@0: }; michael@0: michael@0: nsHTMLCSSUtils::nsHTMLCSSUtils(nsHTMLEditor* aEditor) michael@0: : mHTMLEditor(aEditor) michael@0: , mIsCSSPrefChecked(true) michael@0: { michael@0: // let's retrieve the value of the "CSS editing" pref michael@0: mIsCSSPrefChecked = Preferences::GetBool("editor.use_css", mIsCSSPrefChecked); michael@0: } michael@0: michael@0: nsHTMLCSSUtils::~nsHTMLCSSUtils() michael@0: { michael@0: } michael@0: michael@0: // Answers true if we have some CSS equivalence for the HTML style defined michael@0: // by aProperty and/or aAttribute for the node aNode michael@0: bool michael@0: nsHTMLCSSUtils::IsCSSEditableProperty(nsIDOMNode* aNode, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute) michael@0: { michael@0: NS_ASSERTION(aNode, "Shouldn't you pass aNode? - Bug 214025"); michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(content, false); michael@0: return IsCSSEditableProperty(content, aProperty, aAttribute); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLCSSUtils::IsCSSEditableProperty(nsIContent* aNode, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: michael@0: nsIContent* content = aNode; michael@0: // we need an element node here michael@0: if (content->NodeType() == nsIDOMNode::TEXT_NODE) { michael@0: content = content->GetParent(); michael@0: NS_ENSURE_TRUE(content, false); michael@0: } michael@0: michael@0: nsIAtom *tagName = content->Tag(); michael@0: // brade: shouldn't some of the above go below the next block? michael@0: michael@0: // html inline styles B I TT U STRIKE and COLOR/FACE on FONT michael@0: if (nsEditProperty::b == aProperty michael@0: || nsEditProperty::i == aProperty michael@0: || nsEditProperty::tt == aProperty michael@0: || nsEditProperty::u == aProperty michael@0: || nsEditProperty::strike == aProperty michael@0: || ((nsEditProperty::font == aProperty) && aAttribute && michael@0: (aAttribute->EqualsLiteral("color") || michael@0: aAttribute->EqualsLiteral("face")))) { michael@0: return true; michael@0: } michael@0: michael@0: // ALIGN attribute on elements supporting it michael@0: if (aAttribute && (aAttribute->EqualsLiteral("align")) && michael@0: (nsEditProperty::div == tagName michael@0: || nsEditProperty::p == tagName michael@0: || nsEditProperty::h1 == tagName michael@0: || nsEditProperty::h2 == tagName michael@0: || nsEditProperty::h3 == tagName michael@0: || nsEditProperty::h4 == tagName michael@0: || nsEditProperty::h5 == tagName michael@0: || nsEditProperty::h6 == tagName michael@0: || nsEditProperty::td == tagName michael@0: || nsEditProperty::th == tagName michael@0: || nsEditProperty::table == tagName michael@0: || nsEditProperty::hr == tagName michael@0: // brade: for the above, why not use nsHTMLEditUtils::SupportsAlignAttr michael@0: // brade: but it also checks for tbody, tfoot, thead michael@0: // Let's add the following elements here even if ALIGN has not michael@0: // the same meaning for them michael@0: || nsEditProperty::legend == tagName michael@0: || nsEditProperty::caption == tagName)) { michael@0: return true; michael@0: } michael@0: michael@0: if (aAttribute && (aAttribute->EqualsLiteral("valign")) && michael@0: (nsEditProperty::col == tagName michael@0: || nsEditProperty::colgroup == tagName michael@0: || nsEditProperty::tbody == tagName michael@0: || nsEditProperty::td == tagName michael@0: || nsEditProperty::th == tagName michael@0: || nsEditProperty::tfoot == tagName michael@0: || nsEditProperty::thead == tagName michael@0: || nsEditProperty::tr == tagName)) { michael@0: return true; michael@0: } michael@0: michael@0: // attributes TEXT, BACKGROUND and BGCOLOR on BODY michael@0: if (aAttribute && (nsEditProperty::body == tagName) && michael@0: (aAttribute->EqualsLiteral("text") michael@0: || aAttribute->EqualsLiteral("background") michael@0: || aAttribute->EqualsLiteral("bgcolor"))) { michael@0: return true; michael@0: } michael@0: michael@0: // attribute BGCOLOR on other elements michael@0: if (aAttribute && aAttribute->EqualsLiteral("bgcolor")) { michael@0: return true; michael@0: } michael@0: michael@0: // attributes HEIGHT, WIDTH and NOWRAP on TD and TH michael@0: if (aAttribute && ((nsEditProperty::td == tagName) michael@0: || (nsEditProperty::th == tagName)) && michael@0: (aAttribute->EqualsLiteral("height") michael@0: || aAttribute->EqualsLiteral("width") michael@0: || aAttribute->EqualsLiteral("nowrap"))) { michael@0: return true; michael@0: } michael@0: michael@0: // attributes HEIGHT and WIDTH on TABLE michael@0: if (aAttribute && (nsEditProperty::table == tagName) && michael@0: (aAttribute->EqualsLiteral("height") michael@0: || aAttribute->EqualsLiteral("width"))) { michael@0: return true; michael@0: } michael@0: michael@0: // attributes SIZE and WIDTH on HR michael@0: if (aAttribute && (nsEditProperty::hr == tagName) && michael@0: (aAttribute->EqualsLiteral("size") michael@0: || aAttribute->EqualsLiteral("width"))) { michael@0: return true; michael@0: } michael@0: michael@0: // attribute TYPE on OL UL LI michael@0: if (aAttribute && (nsEditProperty::ol == tagName michael@0: || nsEditProperty::ul == tagName michael@0: || nsEditProperty::li == tagName) && michael@0: aAttribute->EqualsLiteral("type")) { michael@0: return true; michael@0: } michael@0: michael@0: if (aAttribute && nsEditProperty::img == tagName && michael@0: (aAttribute->EqualsLiteral("border") michael@0: || aAttribute->EqualsLiteral("width") michael@0: || aAttribute->EqualsLiteral("height"))) { michael@0: return true; michael@0: } michael@0: michael@0: // other elements that we can align using CSS even if they michael@0: // can't carry the html ALIGN attribute michael@0: if (aAttribute && aAttribute->EqualsLiteral("align") && michael@0: (nsEditProperty::ul == tagName michael@0: || nsEditProperty::ol == tagName michael@0: || nsEditProperty::dl == tagName michael@0: || nsEditProperty::li == tagName michael@0: || nsEditProperty::dd == tagName michael@0: || nsEditProperty::dt == tagName michael@0: || nsEditProperty::address == tagName michael@0: || nsEditProperty::pre == tagName michael@0: || nsEditProperty::ul == tagName)) { michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // the lowest level above the transaction; adds the css declaration "aProperty : aValue" to michael@0: // the inline styles carried by aElement michael@0: nsresult michael@0: nsHTMLCSSUtils::SetCSSProperty(nsIDOMElement *aElement, nsIAtom * aProperty, const nsAString & aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: nsRefPtr txn; michael@0: nsresult result = CreateCSSPropertyTxn(aElement, aProperty, aValue, michael@0: getter_AddRefs(txn), false); michael@0: if (NS_SUCCEEDED(result)) { michael@0: if (aSuppressTransaction) { michael@0: result = txn->DoTransaction(); michael@0: } michael@0: else { michael@0: result = mHTMLEditor->DoTransaction(txn); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::SetCSSPropertyPixels(nsIDOMElement *aElement, michael@0: nsIAtom *aProperty, michael@0: int32_t aIntValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: nsAutoString s; michael@0: s.AppendInt(aIntValue); michael@0: return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px"), michael@0: aSuppressTransaction); michael@0: } michael@0: michael@0: // the lowest level above the transaction; removes the value aValue from the list of values michael@0: // specified for the CSS property aProperty, or totally remove the declaration if this michael@0: // property accepts only one value michael@0: nsresult michael@0: nsHTMLCSSUtils::RemoveCSSProperty(nsIDOMElement *aElement, nsIAtom * aProperty, const nsAString & aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: nsRefPtr txn; michael@0: nsresult result = CreateCSSPropertyTxn(aElement, aProperty, aValue, michael@0: getter_AddRefs(txn), true); michael@0: if (NS_SUCCEEDED(result)) { michael@0: if (aSuppressTransaction) { michael@0: result = txn->DoTransaction(); michael@0: } michael@0: else { michael@0: result = mHTMLEditor->DoTransaction(txn); michael@0: } michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::CreateCSSPropertyTxn(nsIDOMElement *aElement, michael@0: nsIAtom * aAttribute, michael@0: const nsAString& aValue, michael@0: ChangeCSSInlineStyleTxn ** aTxn, michael@0: bool aRemoveProperty) michael@0: { michael@0: NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: *aTxn = new ChangeCSSInlineStyleTxn(); michael@0: NS_ENSURE_TRUE(*aTxn, NS_ERROR_OUT_OF_MEMORY); michael@0: NS_ADDREF(*aTxn); michael@0: return (*aTxn)->Init(mHTMLEditor, aElement, aAttribute, aValue, aRemoveProperty); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::GetSpecifiedProperty(nsIDOMNode *aNode, nsIAtom *aProperty, michael@0: nsAString & aValue) michael@0: { michael@0: return GetCSSInlinePropertyBase(aNode, aProperty, aValue, eSpecified); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::GetComputedProperty(nsIDOMNode *aNode, nsIAtom *aProperty, michael@0: nsAString & aValue) michael@0: { michael@0: return GetCSSInlinePropertyBase(aNode, aProperty, aValue, eComputed); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::GetCSSInlinePropertyBase(nsIDOMNode* aNode, nsIAtom* aProperty, michael@0: nsAString& aValue, michael@0: StyleType aStyleType) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: return GetCSSInlinePropertyBase(node, aProperty, aValue, aStyleType); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::GetCSSInlinePropertyBase(nsINode* aNode, nsIAtom* aProperty, michael@0: nsAString& aValue, michael@0: StyleType aStyleType) michael@0: { michael@0: MOZ_ASSERT(aNode && aProperty); michael@0: aValue.Truncate(); michael@0: michael@0: nsCOMPtr element = GetElementContainerOrSelf(aNode); michael@0: NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (aStyleType == eComputed) { michael@0: // Get the all the computed css styles attached to the element node michael@0: nsRefPtr cssDecl = GetComputedStyle(element); michael@0: NS_ENSURE_STATE(cssDecl); michael@0: michael@0: // from these declarations, get the one we want and that one only michael@0: MOZ_ALWAYS_TRUE(NS_SUCCEEDED( michael@0: cssDecl->GetPropertyValue(nsDependentAtomString(aProperty), aValue))); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(aStyleType == eSpecified); michael@0: nsRefPtr rule = element->GetInlineStyleRule(); michael@0: if (!rule) { michael@0: return NS_OK; michael@0: } michael@0: nsCSSProperty prop = michael@0: nsCSSProps::LookupProperty(nsDependentAtomString(aProperty), michael@0: nsCSSProps::eEnabledForAllContent); michael@0: MOZ_ASSERT(prop != eCSSProperty_UNKNOWN); michael@0: rule->GetDeclaration()->GetValue(prop, aValue); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLCSSUtils::GetComputedStyle(nsIDOMElement* aElement) michael@0: { michael@0: nsCOMPtr element = do_QueryInterface(aElement); michael@0: return GetComputedStyle(element); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLCSSUtils::GetComputedStyle(dom::Element* aElement) michael@0: { michael@0: MOZ_ASSERT(aElement); michael@0: michael@0: nsIDocument* doc = aElement->GetCurrentDoc(); michael@0: NS_ENSURE_TRUE(doc, nullptr); michael@0: michael@0: nsIPresShell* presShell = doc->GetShell(); michael@0: NS_ENSURE_TRUE(presShell, nullptr); michael@0: michael@0: nsRefPtr style = michael@0: NS_NewComputedDOMStyle(aElement, EmptyString(), presShell); michael@0: michael@0: return style.forget(); michael@0: } michael@0: michael@0: // remove the CSS style "aProperty : aPropertyValue" and possibly remove the whole node michael@0: // if it is a span and if its only attribute is _moz_dirty michael@0: nsresult michael@0: nsHTMLCSSUtils::RemoveCSSInlineStyle(nsIDOMNode *aNode, nsIAtom *aProperty, const nsAString & aPropertyValue) michael@0: { michael@0: nsCOMPtr elem = do_QueryInterface(aNode); michael@0: michael@0: // remove the property from the style attribute michael@0: nsresult res = RemoveCSSProperty(elem, aProperty, aPropertyValue, false); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: if (!element || !element->IsHTML(nsGkAtoms::span) || michael@0: nsHTMLEditor::HasAttributes(element)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: return mHTMLEditor->RemoveContainer(aNode); michael@0: } michael@0: michael@0: // Answers true is the property can be removed by setting a "none" CSS value michael@0: // on a node michael@0: bool michael@0: nsHTMLCSSUtils::IsCSSInvertable(nsIAtom *aProperty, const nsAString *aAttribute) michael@0: { michael@0: return bool(nsEditProperty::b == aProperty); michael@0: } michael@0: michael@0: // Get the default browser background color if we need it for GetCSSBackgroundColorState michael@0: void michael@0: nsHTMLCSSUtils::GetDefaultBackgroundColor(nsAString & aColor) michael@0: { michael@0: if (Preferences::GetBool("editor.use_custom_colors", false)) { michael@0: nsresult rv = Preferences::GetString("editor.background_color", &aColor); michael@0: // XXX Why don't you validate the pref value? michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("failed to get editor.background_color"); michael@0: aColor.AssignLiteral("#ffffff"); // Default to white michael@0: } michael@0: return; michael@0: } michael@0: michael@0: if (Preferences::GetBool("browser.display.use_system_colors", false)) { michael@0: return; michael@0: } michael@0: michael@0: nsresult rv = michael@0: Preferences::GetString("browser.display.background_color", &aColor); michael@0: // XXX Why don't you validate the pref value? michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("failed to get browser.display.background_color"); michael@0: aColor.AssignLiteral("#ffffff"); // Default to white michael@0: } michael@0: } michael@0: michael@0: // Get the default length unit used for CSS Indent/Outdent michael@0: void michael@0: nsHTMLCSSUtils::GetDefaultLengthUnit(nsAString & aLengthUnit) michael@0: { michael@0: nsresult rv = michael@0: Preferences::GetString("editor.css.default_length_unit", &aLengthUnit); michael@0: // XXX Why don't you validate the pref value? michael@0: if (NS_FAILED(rv)) { michael@0: aLengthUnit.AssignLiteral("px"); michael@0: } michael@0: } michael@0: michael@0: // Unfortunately, CSSStyleDeclaration::GetPropertyCSSValue is not yet implemented... michael@0: // We need then a way to determine the number part and the unit from aString, aString michael@0: // being the result of a GetPropertyValue query... michael@0: void michael@0: nsHTMLCSSUtils::ParseLength(const nsAString & aString, float * aValue, nsIAtom ** aUnit) michael@0: { michael@0: nsAString::const_iterator iter; michael@0: aString.BeginReading(iter); michael@0: michael@0: float a = 10.0f , b = 1.0f, value = 0; michael@0: int8_t sign = 1; michael@0: int32_t i = 0, j = aString.Length(); michael@0: char16_t c; michael@0: bool floatingPointFound = false; michael@0: c = *iter; michael@0: if (char16_t('-') == c) { sign = -1; iter++; i++; } michael@0: else if (char16_t('+') == c) { iter++; i++; } michael@0: while (i < j) { michael@0: c = *iter; michael@0: if ((char16_t('0') == c) || michael@0: (char16_t('1') == c) || michael@0: (char16_t('2') == c) || michael@0: (char16_t('3') == c) || michael@0: (char16_t('4') == c) || michael@0: (char16_t('5') == c) || michael@0: (char16_t('6') == c) || michael@0: (char16_t('7') == c) || michael@0: (char16_t('8') == c) || michael@0: (char16_t('9') == c)) { michael@0: value = (value * a) + (b * (c - char16_t('0'))); michael@0: b = b / 10 * a; michael@0: } michael@0: else if (!floatingPointFound && (char16_t('.') == c)) { michael@0: floatingPointFound = true; michael@0: a = 1.0f; b = 0.1f; michael@0: } michael@0: else break; michael@0: iter++; michael@0: i++; michael@0: } michael@0: *aValue = value * sign; michael@0: *aUnit = NS_NewAtom(StringTail(aString, j-i)).take(); michael@0: } michael@0: michael@0: void michael@0: nsHTMLCSSUtils::GetCSSPropertyAtom(nsCSSEditableProperty aProperty, nsIAtom ** aAtom) michael@0: { michael@0: *aAtom = nullptr; michael@0: switch (aProperty) { michael@0: case eCSSEditableProperty_background_color: michael@0: *aAtom = nsEditProperty::cssBackgroundColor; michael@0: break; michael@0: case eCSSEditableProperty_background_image: michael@0: *aAtom = nsEditProperty::cssBackgroundImage; michael@0: break; michael@0: case eCSSEditableProperty_border: michael@0: *aAtom = nsEditProperty::cssBorder; michael@0: break; michael@0: case eCSSEditableProperty_caption_side: michael@0: *aAtom = nsEditProperty::cssCaptionSide; michael@0: break; michael@0: case eCSSEditableProperty_color: michael@0: *aAtom = nsEditProperty::cssColor; michael@0: break; michael@0: case eCSSEditableProperty_float: michael@0: *aAtom = nsEditProperty::cssFloat; michael@0: break; michael@0: case eCSSEditableProperty_font_family: michael@0: *aAtom = nsEditProperty::cssFontFamily; michael@0: break; michael@0: case eCSSEditableProperty_font_size: michael@0: *aAtom = nsEditProperty::cssFontSize; michael@0: break; michael@0: case eCSSEditableProperty_font_style: michael@0: *aAtom = nsEditProperty::cssFontStyle; michael@0: break; michael@0: case eCSSEditableProperty_font_weight: michael@0: *aAtom = nsEditProperty::cssFontWeight; michael@0: break; michael@0: case eCSSEditableProperty_height: michael@0: *aAtom = nsEditProperty::cssHeight; michael@0: break; michael@0: case eCSSEditableProperty_list_style_type: michael@0: *aAtom = nsEditProperty::cssListStyleType; michael@0: break; michael@0: case eCSSEditableProperty_margin_left: michael@0: *aAtom = nsEditProperty::cssMarginLeft; michael@0: break; michael@0: case eCSSEditableProperty_margin_right: michael@0: *aAtom = nsEditProperty::cssMarginRight; michael@0: break; michael@0: case eCSSEditableProperty_text_align: michael@0: *aAtom = nsEditProperty::cssTextAlign; michael@0: break; michael@0: case eCSSEditableProperty_text_decoration: michael@0: *aAtom = nsEditProperty::cssTextDecoration; michael@0: break; michael@0: case eCSSEditableProperty_vertical_align: michael@0: *aAtom = nsEditProperty::cssVerticalAlign; michael@0: break; michael@0: case eCSSEditableProperty_whitespace: michael@0: *aAtom = nsEditProperty::cssWhitespace; michael@0: break; michael@0: case eCSSEditableProperty_width: michael@0: *aAtom = nsEditProperty::cssWidth; michael@0: break; michael@0: case eCSSEditableProperty_NONE: michael@0: // intentionally empty michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Populate aProperty and aValueArray with the CSS declarations equivalent to the michael@0: // value aValue according to the equivalence table aEquivTable michael@0: void michael@0: nsHTMLCSSUtils::BuildCSSDeclarations(nsTArray & aPropertyArray, michael@0: nsTArray & aValueArray, michael@0: const CSSEquivTable * aEquivTable, michael@0: const nsAString * aValue, michael@0: bool aGetOrRemoveRequest) michael@0: { michael@0: // clear arrays michael@0: aPropertyArray.Clear(); michael@0: aValueArray.Clear(); michael@0: michael@0: // if we have an input value, let's use it michael@0: nsAutoString value, lowerCasedValue; michael@0: if (aValue) { michael@0: value.Assign(*aValue); michael@0: lowerCasedValue.Assign(*aValue); michael@0: ToLowerCase(lowerCasedValue); michael@0: } michael@0: michael@0: int8_t index = 0; michael@0: nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty; michael@0: while (cssProperty) { michael@0: if (!aGetOrRemoveRequest|| aEquivTable[index].gettable) { michael@0: nsAutoString cssValue, cssPropertyString; michael@0: nsIAtom * cssPropertyAtom; michael@0: // find the equivalent css value for the index-th property in michael@0: // the equivalence table michael@0: (*aEquivTable[index].processValueFunctor) ((!aGetOrRemoveRequest || aEquivTable[index].caseSensitiveValue) ? &value : &lowerCasedValue, michael@0: cssValue, michael@0: aEquivTable[index].defaultValue, michael@0: aEquivTable[index].prependValue, michael@0: aEquivTable[index].appendValue); michael@0: GetCSSPropertyAtom(cssProperty, &cssPropertyAtom); michael@0: aPropertyArray.AppendElement(cssPropertyAtom); michael@0: aValueArray.AppendElement(cssValue); michael@0: } michael@0: index++; michael@0: cssProperty = aEquivTable[index].cssProperty; michael@0: } michael@0: } michael@0: michael@0: // Populate cssPropertyArray and cssValueArray with the declarations equivalent michael@0: // to aHTMLProperty/aAttribute/aValue for the node aNode michael@0: void michael@0: nsHTMLCSSUtils::GenerateCSSDeclarationsFromHTMLStyle(dom::Element* aElement, michael@0: nsIAtom* aHTMLProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue, michael@0: nsTArray& cssPropertyArray, michael@0: nsTArray& cssValueArray, michael@0: bool aGetOrRemoveRequest) michael@0: { michael@0: MOZ_ASSERT(aElement); michael@0: nsIAtom* tagName = aElement->Tag(); michael@0: const nsHTMLCSSUtils::CSSEquivTable* equivTable = nullptr; michael@0: michael@0: if (nsEditProperty::b == aHTMLProperty) { michael@0: equivTable = boldEquivTable; michael@0: } else if (nsEditProperty::i == aHTMLProperty) { michael@0: equivTable = italicEquivTable; michael@0: } else if (nsEditProperty::u == aHTMLProperty) { michael@0: equivTable = underlineEquivTable; michael@0: } else if (nsEditProperty::strike == aHTMLProperty) { michael@0: equivTable = strikeEquivTable; michael@0: } else if (nsEditProperty::tt == aHTMLProperty) { michael@0: equivTable = ttEquivTable; michael@0: } else if (aAttribute) { michael@0: if (nsEditProperty::font == aHTMLProperty && michael@0: aAttribute->EqualsLiteral("color")) { michael@0: equivTable = fontColorEquivTable; michael@0: } else if (nsEditProperty::font == aHTMLProperty && michael@0: aAttribute->EqualsLiteral("face")) { michael@0: equivTable = fontFaceEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("bgcolor")) { michael@0: equivTable = bgcolorEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("background")) { michael@0: equivTable = backgroundImageEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("text")) { michael@0: equivTable = textColorEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("border")) { michael@0: equivTable = borderEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("align")) { michael@0: if (nsEditProperty::table == tagName) { michael@0: equivTable = tableAlignEquivTable; michael@0: } else if (nsEditProperty::hr == tagName) { michael@0: equivTable = hrAlignEquivTable; michael@0: } else if (nsEditProperty::legend == tagName || michael@0: nsEditProperty::caption == tagName) { michael@0: equivTable = captionAlignEquivTable; michael@0: } else { michael@0: equivTable = textAlignEquivTable; michael@0: } michael@0: } else if (aAttribute->EqualsLiteral("valign")) { michael@0: equivTable = verticalAlignEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("nowrap")) { michael@0: equivTable = nowrapEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("width")) { michael@0: equivTable = widthEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("height") || michael@0: (nsEditProperty::hr == tagName && michael@0: aAttribute->EqualsLiteral("size"))) { michael@0: equivTable = heightEquivTable; michael@0: } else if (aAttribute->EqualsLiteral("type") && michael@0: (nsEditProperty::ol == tagName michael@0: || nsEditProperty::ul == tagName michael@0: || nsEditProperty::li == tagName)) { michael@0: equivTable = listStyleTypeEquivTable; michael@0: } michael@0: } michael@0: if (equivTable) { michael@0: BuildCSSDeclarations(cssPropertyArray, cssValueArray, equivTable, michael@0: aValue, aGetOrRemoveRequest); michael@0: } michael@0: } michael@0: michael@0: // Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/ michael@0: // aValue for the node, and return in aCount the number of CSS properties set michael@0: // by the call. The dom::Element version returns aCount instead. michael@0: int32_t michael@0: nsHTMLCSSUtils::SetCSSEquivalentToHTMLStyle(dom::Element* aElement, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: MOZ_ASSERT(aElement && aProperty); michael@0: MOZ_ASSERT_IF(aAttribute, aValue); michael@0: int32_t count; michael@0: // This can only fail if SetCSSProperty fails, which should only happen if michael@0: // something is pretty badly wrong. In this case we assert so that hopefully michael@0: // someone will notice, but there's nothing more sensible to do than just michael@0: // return the count and carry on. michael@0: nsresult res = SetCSSEquivalentToHTMLStyle(aElement->AsDOMNode(), michael@0: aProperty, aAttribute, michael@0: aValue, &count, michael@0: aSuppressTransaction); michael@0: NS_ASSERTION(NS_SUCCEEDED(res), "SetCSSEquivalentToHTMLStyle failed"); michael@0: NS_ENSURE_SUCCESS(res, count); michael@0: return count; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::SetCSSEquivalentToHTMLStyle(nsIDOMNode * aNode, michael@0: nsIAtom *aHTMLProperty, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue, michael@0: int32_t * aCount, michael@0: bool aSuppressTransaction) michael@0: { michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: *aCount = 0; michael@0: if (!element || !IsCSSEditableProperty(element, aHTMLProperty, aAttribute)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we can apply the styles only if the node is an element and if we have michael@0: // an equivalence for the requested HTML style in this implementation michael@0: michael@0: // Find the CSS equivalence to the HTML style michael@0: nsTArray cssPropertyArray; michael@0: nsTArray cssValueArray; michael@0: GenerateCSSDeclarationsFromHTMLStyle(element, aHTMLProperty, aAttribute, michael@0: aValue, cssPropertyArray, cssValueArray, michael@0: false); michael@0: michael@0: nsCOMPtr domElement = do_QueryInterface(element); michael@0: // set the individual CSS inline styles michael@0: *aCount = cssPropertyArray.Length(); michael@0: for (int32_t index = 0; index < *aCount; index++) { michael@0: nsresult res = SetCSSProperty(domElement, cssPropertyArray[index], michael@0: cssValueArray[index], aSuppressTransaction); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Remove from aNode the CSS inline style equivalent to HTMLProperty/aAttribute/aValue for the node michael@0: nsresult michael@0: nsHTMLCSSUtils::RemoveCSSEquivalentToHTMLStyle(nsIDOMNode * aNode, michael@0: nsIAtom *aHTMLProperty, michael@0: const nsAString *aAttribute, michael@0: const nsAString *aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(element, NS_OK); michael@0: michael@0: return RemoveCSSEquivalentToHTMLStyle(element, aHTMLProperty, aAttribute, michael@0: aValue, aSuppressTransaction); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::RemoveCSSEquivalentToHTMLStyle(dom::Element* aElement, michael@0: nsIAtom* aHTMLProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString* aValue, michael@0: bool aSuppressTransaction) michael@0: { michael@0: MOZ_ASSERT(aElement); michael@0: michael@0: if (!IsCSSEditableProperty(aElement, aHTMLProperty, aAttribute)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // we can apply the styles only if the node is an element and if we have michael@0: // an equivalence for the requested HTML style in this implementation michael@0: michael@0: // Find the CSS equivalence to the HTML style michael@0: nsTArray cssPropertyArray; michael@0: nsTArray cssValueArray; michael@0: GenerateCSSDeclarationsFromHTMLStyle(aElement, aHTMLProperty, aAttribute, michael@0: aValue, cssPropertyArray, cssValueArray, michael@0: true); michael@0: michael@0: nsCOMPtr domElement = do_QueryInterface(aElement); michael@0: // remove the individual CSS inline styles michael@0: int32_t count = cssPropertyArray.Length(); michael@0: for (int32_t index = 0; index < count; index++) { michael@0: nsresult res = RemoveCSSProperty(domElement, michael@0: cssPropertyArray[index], michael@0: cssValueArray[index], michael@0: aSuppressTransaction); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // returns in aValueString the list of values for the CSS equivalences to michael@0: // the HTML style aHTMLProperty/aAttribute/aValueString for the node aNode; michael@0: // the value of aStyleType controls the styles we retrieve : specified or michael@0: // computed. michael@0: nsresult michael@0: nsHTMLCSSUtils::GetCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode, michael@0: nsIAtom *aHTMLProperty, michael@0: const nsAString *aAttribute, michael@0: nsAString & aValueString, michael@0: StyleType aStyleType) michael@0: { michael@0: aValueString.Truncate(); michael@0: nsCOMPtr theElement = GetElementContainerOrSelf(aNode); michael@0: NS_ENSURE_TRUE(theElement, NS_ERROR_NULL_POINTER); michael@0: michael@0: if (!theElement || !IsCSSEditableProperty(theElement, aHTMLProperty, aAttribute)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Yes, the requested HTML style has a CSS equivalence in this implementation michael@0: nsTArray cssPropertyArray; michael@0: nsTArray cssValueArray; michael@0: // get the CSS equivalence with last param true indicating we want only the michael@0: // "gettable" properties michael@0: GenerateCSSDeclarationsFromHTMLStyle(theElement, aHTMLProperty, aAttribute, nullptr, michael@0: cssPropertyArray, cssValueArray, true); michael@0: int32_t count = cssPropertyArray.Length(); michael@0: for (int32_t index = 0; index < count; index++) { michael@0: nsAutoString valueString; michael@0: // retrieve the specified/computed value of the property michael@0: nsresult res = GetCSSInlinePropertyBase(theElement, cssPropertyArray[index], michael@0: valueString, aStyleType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: // append the value to aValueString (possibly with a leading whitespace) michael@0: if (index) { michael@0: aValueString.Append(char16_t(' ')); michael@0: } michael@0: aValueString.Append(valueString); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Does the node aNode (or its parent, if it's not an element node) have a CSS michael@0: // style equivalent to the HTML style aHTMLProperty/aHTMLAttribute/valueString? michael@0: // The value of aStyleType controls the styles we retrieve: specified or michael@0: // computed. The return value aIsSet is true if the CSS styles are set. michael@0: // michael@0: // The nsIContent variant returns aIsSet instead of using an out parameter, and michael@0: // does not modify aValue. michael@0: bool michael@0: nsHTMLCSSUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsIContent* aContent, michael@0: nsIAtom* aProperty, michael@0: const nsAString* aAttribute, michael@0: const nsAString& aValue, michael@0: StyleType aStyleType) michael@0: { michael@0: MOZ_ASSERT(aContent && aProperty); michael@0: bool isSet; michael@0: nsAutoString value(aValue); michael@0: nsresult res = IsCSSEquivalentToHTMLInlineStyleSet(aContent->AsDOMNode(), michael@0: aProperty, aAttribute, michael@0: isSet, value, aStyleType); michael@0: NS_ASSERTION(NS_SUCCEEDED(res), "IsCSSEquivalentToHTMLInlineStyleSet failed"); michael@0: NS_ENSURE_SUCCESS(res, false); michael@0: return isSet; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsIDOMNode *aNode, michael@0: nsIAtom *aHTMLProperty, michael@0: const nsAString *aHTMLAttribute, michael@0: bool& aIsSet, michael@0: nsAString& valueString, michael@0: StyleType aStyleType) michael@0: { michael@0: NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsAutoString htmlValueString(valueString); michael@0: aIsSet = false; michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: do { michael@0: valueString.Assign(htmlValueString); michael@0: // get the value of the CSS equivalent styles michael@0: nsresult res = GetCSSEquivalentToHTMLInlineStyleSet(node, aHTMLProperty, aHTMLAttribute, michael@0: valueString, aStyleType); michael@0: NS_ENSURE_SUCCESS(res, res); michael@0: michael@0: // early way out if we can michael@0: if (valueString.IsEmpty()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (nsEditProperty::b == aHTMLProperty) { michael@0: if (valueString.EqualsLiteral("bold")) { michael@0: aIsSet = true; michael@0: } else if (valueString.EqualsLiteral("normal")) { michael@0: aIsSet = false; michael@0: } else if (valueString.EqualsLiteral("bolder")) { michael@0: aIsSet = true; michael@0: valueString.AssignLiteral("bold"); michael@0: } else { michael@0: int32_t weight = 0; michael@0: nsresult errorCode; michael@0: nsAutoString value(valueString); michael@0: weight = value.ToInteger(&errorCode); michael@0: if (400 < weight) { michael@0: aIsSet = true; michael@0: valueString.AssignLiteral("bold"); michael@0: } else { michael@0: aIsSet = false; michael@0: valueString.AssignLiteral("normal"); michael@0: } michael@0: } michael@0: } else if (nsEditProperty::i == aHTMLProperty) { michael@0: if (valueString.EqualsLiteral("italic") || michael@0: valueString.EqualsLiteral("oblique")) { michael@0: aIsSet = true; michael@0: } michael@0: } else if (nsEditProperty::u == aHTMLProperty) { michael@0: nsAutoString val; michael@0: val.AssignLiteral("underline"); michael@0: aIsSet = bool(ChangeCSSInlineStyleTxn::ValueIncludes(valueString, val, false)); michael@0: } else if (nsEditProperty::strike == aHTMLProperty) { michael@0: nsAutoString val; michael@0: val.AssignLiteral("line-through"); michael@0: aIsSet = bool(ChangeCSSInlineStyleTxn::ValueIncludes(valueString, val, false)); michael@0: } else if (aHTMLAttribute && michael@0: ((nsEditProperty::font == aHTMLProperty && michael@0: aHTMLAttribute->EqualsLiteral("color")) || michael@0: aHTMLAttribute->EqualsLiteral("bgcolor"))) { michael@0: if (htmlValueString.IsEmpty()) { michael@0: aIsSet = true; michael@0: } else { michael@0: nscolor rgba; michael@0: nsAutoString subStr; michael@0: htmlValueString.Right(subStr, htmlValueString.Length() - 1); michael@0: if (NS_ColorNameToRGB(htmlValueString, &rgba) || michael@0: NS_HexToRGB(subStr, &rgba)) { michael@0: nsAutoString htmlColor, tmpStr; michael@0: michael@0: if (NS_GET_A(rgba) != 255) { michael@0: // This should only be hit by the "transparent" keyword, which michael@0: // currently serializes to "transparent" (not "rgba(0, 0, 0, 0)"). michael@0: MOZ_ASSERT(NS_GET_R(rgba) == 0 && NS_GET_G(rgba) == 0 && michael@0: NS_GET_B(rgba) == 0 && NS_GET_A(rgba) == 0); michael@0: htmlColor.AppendLiteral("transparent"); michael@0: } else { michael@0: htmlColor.AppendLiteral("rgb("); michael@0: michael@0: NS_NAMED_LITERAL_STRING(comma, ", "); michael@0: michael@0: tmpStr.AppendInt(NS_GET_R(rgba), 10); michael@0: htmlColor.Append(tmpStr + comma); michael@0: michael@0: tmpStr.Truncate(); michael@0: tmpStr.AppendInt(NS_GET_G(rgba), 10); michael@0: htmlColor.Append(tmpStr + comma); michael@0: michael@0: tmpStr.Truncate(); michael@0: tmpStr.AppendInt(NS_GET_B(rgba), 10); michael@0: htmlColor.Append(tmpStr); michael@0: michael@0: htmlColor.Append(char16_t(')')); michael@0: } michael@0: michael@0: aIsSet = htmlColor.Equals(valueString, michael@0: nsCaseInsensitiveStringComparator()); michael@0: } else { michael@0: aIsSet = htmlValueString.Equals(valueString, michael@0: nsCaseInsensitiveStringComparator()); michael@0: } michael@0: } michael@0: } else if (nsEditProperty::tt == aHTMLProperty) { michael@0: aIsSet = StringBeginsWith(valueString, NS_LITERAL_STRING("monospace")); michael@0: } else if (nsEditProperty::font == aHTMLProperty && aHTMLAttribute && michael@0: aHTMLAttribute->EqualsLiteral("face")) { michael@0: if (!htmlValueString.IsEmpty()) { michael@0: const char16_t commaSpace[] = { char16_t(','), char16_t(' '), 0 }; michael@0: const char16_t comma[] = { char16_t(','), 0 }; michael@0: htmlValueString.ReplaceSubstring(commaSpace, comma); michael@0: nsAutoString valueStringNorm(valueString); michael@0: valueStringNorm.ReplaceSubstring(commaSpace, comma); michael@0: aIsSet = htmlValueString.Equals(valueStringNorm, michael@0: nsCaseInsensitiveStringComparator()); michael@0: } else { michael@0: // ignore this, it's TT or our default michael@0: nsAutoString valueStringLower; michael@0: ToLowerCase(valueString, valueStringLower); michael@0: aIsSet = !valueStringLower.EqualsLiteral("monospace") && michael@0: !valueStringLower.EqualsLiteral("serif"); michael@0: } michael@0: return NS_OK; michael@0: } else if (aHTMLAttribute && aHTMLAttribute->EqualsLiteral("align")) { michael@0: aIsSet = true; michael@0: } else { michael@0: aIsSet = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (!htmlValueString.IsEmpty() && michael@0: htmlValueString.Equals(valueString, michael@0: nsCaseInsensitiveStringComparator())) { michael@0: aIsSet = true; michael@0: } michael@0: michael@0: if (htmlValueString.EqualsLiteral("-moz-editor-invert-value")) { michael@0: aIsSet = !aIsSet; michael@0: } michael@0: michael@0: if (nsEditProperty::u == aHTMLProperty || nsEditProperty::strike == aHTMLProperty) { michael@0: // unfortunately, the value of the text-decoration property is not inherited. michael@0: // that means that we have to look at ancestors of node to see if they are underlined michael@0: node = node->GetParentElement(); // set to null if it's not a dom element michael@0: } michael@0: } while ((nsEditProperty::u == aHTMLProperty || nsEditProperty::strike == aHTMLProperty) && michael@0: !aIsSet && node); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHTMLCSSUtils::SetCSSEnabled(bool aIsCSSPrefChecked) michael@0: { michael@0: mIsCSSPrefChecked = aIsCSSPrefChecked; michael@0: } michael@0: michael@0: bool michael@0: nsHTMLCSSUtils::IsCSSPrefChecked() michael@0: { michael@0: return mIsCSSPrefChecked ; michael@0: } michael@0: michael@0: // ElementsSameStyle compares two elements and checks if they have the same michael@0: // specified CSS declarations in the STYLE attribute michael@0: // The answer is always negative if at least one of them carries an ID or a class michael@0: bool michael@0: nsHTMLCSSUtils::ElementsSameStyle(nsIDOMNode *aFirstNode, nsIDOMNode *aSecondNode) michael@0: { michael@0: nsCOMPtr firstElement = do_QueryInterface(aFirstNode); michael@0: nsCOMPtr secondElement = do_QueryInterface(aSecondNode); michael@0: michael@0: NS_ASSERTION((firstElement && secondElement), "Non element nodes passed to ElementsSameStyle."); michael@0: NS_ENSURE_TRUE(firstElement, false); michael@0: NS_ENSURE_TRUE(secondElement, false); michael@0: michael@0: return ElementsSameStyle(firstElement, secondElement); michael@0: } michael@0: michael@0: bool michael@0: nsHTMLCSSUtils::ElementsSameStyle(dom::Element* aFirstElement, michael@0: dom::Element* aSecondElement) michael@0: { michael@0: MOZ_ASSERT(aFirstElement); michael@0: MOZ_ASSERT(aSecondElement); michael@0: michael@0: if (aFirstElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id) || michael@0: aSecondElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id)) { michael@0: // at least one of the spans carries an ID ; suspect a CSS rule applies to it and michael@0: // refuse to merge the nodes michael@0: return false; michael@0: } michael@0: michael@0: nsAutoString firstClass, secondClass; michael@0: bool isFirstClassSet = aFirstElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, firstClass); michael@0: bool isSecondClassSet = aSecondElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, secondClass); michael@0: if (isFirstClassSet && isSecondClassSet) { michael@0: // both spans carry a class, let's compare them michael@0: if (!firstClass.Equals(secondClass)) { michael@0: // WARNING : technically, the comparison just above is questionable : michael@0: // from a pure HTML/CSS point of view class="a b" is NOT the same than michael@0: // class="b a" because a CSS rule could test the exact value of the class michael@0: // attribute to be "a b" for instance ; from a user's point of view, a michael@0: // wysiwyg editor should probably NOT make any difference. CSS people michael@0: // need to discuss this issue before any modification. michael@0: return false; michael@0: } michael@0: } else if (isFirstClassSet || isSecondClassSet) { michael@0: // one span only carries a class, early way out michael@0: return false; michael@0: } michael@0: michael@0: nsCOMPtr firstCSSDecl, secondCSSDecl; michael@0: uint32_t firstLength, secondLength; michael@0: nsresult rv = GetInlineStyles(aFirstElement, getter_AddRefs(firstCSSDecl), &firstLength); michael@0: if (NS_FAILED(rv) || !firstCSSDecl) { michael@0: return false; michael@0: } michael@0: rv = GetInlineStyles(aSecondElement, getter_AddRefs(secondCSSDecl), &secondLength); michael@0: if (NS_FAILED(rv) || !secondCSSDecl) { michael@0: return false; michael@0: } michael@0: michael@0: if (firstLength != secondLength) { michael@0: // early way out if we can michael@0: return false; michael@0: } michael@0: michael@0: if (!firstLength) { michael@0: // no inline style ! michael@0: return true; michael@0: } michael@0: michael@0: nsAutoString propertyNameString; michael@0: nsAutoString firstValue, secondValue; michael@0: for (uint32_t i = 0; i < firstLength; i++) { michael@0: firstCSSDecl->Item(i, propertyNameString); michael@0: firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); michael@0: secondCSSDecl->GetPropertyValue(propertyNameString, secondValue); michael@0: if (!firstValue.Equals(secondValue)) { michael@0: return false; michael@0: } michael@0: } michael@0: for (uint32_t i = 0; i < secondLength; i++) { michael@0: secondCSSDecl->Item(i, propertyNameString); michael@0: secondCSSDecl->GetPropertyValue(propertyNameString, secondValue); michael@0: firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); michael@0: if (!firstValue.Equals(secondValue)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::GetInlineStyles(dom::Element* aElement, michael@0: nsIDOMCSSStyleDeclaration** aCssDecl, michael@0: uint32_t* aLength) michael@0: { michael@0: return GetInlineStyles(static_cast(aElement), aCssDecl, aLength); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::GetInlineStyles(nsIDOMElement* aElement, michael@0: nsIDOMCSSStyleDeclaration** aCssDecl, michael@0: uint32_t* aLength) michael@0: { michael@0: return GetInlineStyles(static_cast(aElement), aCssDecl, aLength); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::GetInlineStyles(nsISupports *aElement, michael@0: nsIDOMCSSStyleDeclaration **aCssDecl, michael@0: uint32_t *aLength) michael@0: { michael@0: NS_ENSURE_TRUE(aElement && aLength, NS_ERROR_NULL_POINTER); michael@0: *aLength = 0; michael@0: nsCOMPtr inlineStyles = do_QueryInterface(aElement); michael@0: NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); michael@0: michael@0: nsresult res = inlineStyles->GetStyle(aCssDecl); michael@0: NS_ENSURE_SUCCESS(res, NS_ERROR_NULL_POINTER); michael@0: MOZ_ASSERT(*aCssDecl); michael@0: michael@0: (*aCssDecl)->GetLength(aLength); michael@0: return NS_OK; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHTMLCSSUtils::GetElementContainerOrSelf(nsIDOMNode* aNode) michael@0: { michael@0: nsCOMPtr node = do_QueryInterface(aNode); michael@0: NS_ENSURE_TRUE(node, nullptr); michael@0: nsCOMPtr element = michael@0: do_QueryInterface(GetElementContainerOrSelf(node)); michael@0: return element.forget(); michael@0: } michael@0: michael@0: dom::Element* michael@0: nsHTMLCSSUtils::GetElementContainerOrSelf(nsINode* aNode) michael@0: { michael@0: MOZ_ASSERT(aNode); michael@0: if (nsIDOMNode::DOCUMENT_NODE == aNode->NodeType()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsINode* node = aNode; michael@0: // Loop until we find an element. michael@0: while (node && !node->IsElement()) { michael@0: node = node->GetParentNode(); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(node, nullptr); michael@0: return node->AsElement(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::SetCSSProperty(nsIDOMElement * aElement, michael@0: const nsAString & aProperty, michael@0: const nsAString & aValue) michael@0: { michael@0: nsCOMPtr cssDecl; michael@0: uint32_t length; michael@0: nsresult res = GetInlineStyles(aElement, getter_AddRefs(cssDecl), &length); michael@0: if (NS_FAILED(res) || !cssDecl) return res; michael@0: michael@0: return cssDecl->SetProperty(aProperty, michael@0: aValue, michael@0: EmptyString()); michael@0: } michael@0: michael@0: nsresult michael@0: nsHTMLCSSUtils::SetCSSPropertyPixels(nsIDOMElement * aElement, michael@0: const nsAString & aProperty, michael@0: int32_t aIntValue) michael@0: { michael@0: nsAutoString s; michael@0: s.AppendInt(aIntValue); michael@0: return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px")); michael@0: }