1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/libeditor/html/nsHTMLCSSUtils.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1415 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "ChangeCSSInlineStyleTxn.h" 1.10 +#include "EditTxn.h" 1.11 +#include "mozilla/Assertions.h" 1.12 +#include "mozilla/Preferences.h" 1.13 +#include "mozilla/css/Declaration.h" 1.14 +#include "mozilla/css/StyleRule.h" 1.15 +#include "mozilla/dom/Element.h" 1.16 +#include "mozilla/mozalloc.h" 1.17 +#include "nsAString.h" 1.18 +#include "nsAutoPtr.h" 1.19 +#include "nsCOMPtr.h" 1.20 +#include "nsColor.h" 1.21 +#include "nsComputedDOMStyle.h" 1.22 +#include "nsDebug.h" 1.23 +#include "nsDependentSubstring.h" 1.24 +#include "nsEditProperty.h" 1.25 +#include "nsError.h" 1.26 +#include "nsGkAtoms.h" 1.27 +#include "nsHTMLCSSUtils.h" 1.28 +#include "nsHTMLEditor.h" 1.29 +#include "nsIAtom.h" 1.30 +#include "nsIContent.h" 1.31 +#include "nsIDOMCSSStyleDeclaration.h" 1.32 +#include "nsIDOMElement.h" 1.33 +#include "nsIDOMElementCSSInlineStyle.h" 1.34 +#include "nsIDOMNode.h" 1.35 +#include "nsIDOMWindow.h" 1.36 +#include "nsIDocument.h" 1.37 +#include "nsIEditor.h" 1.38 +#include "nsINode.h" 1.39 +#include "nsISupportsImpl.h" 1.40 +#include "nsISupportsUtils.h" 1.41 +#include "nsLiteralString.h" 1.42 +#include "nsPIDOMWindow.h" 1.43 +#include "nsReadableUtils.h" 1.44 +#include "nsString.h" 1.45 +#include "nsStringFwd.h" 1.46 +#include "nsStringIterator.h" 1.47 +#include "nsSubstringTuple.h" 1.48 +#include "nsUnicharUtils.h" 1.49 + 1.50 +using namespace mozilla; 1.51 + 1.52 +static 1.53 +void ProcessBValue(const nsAString * aInputString, nsAString & aOutputString, 1.54 + const char * aDefaultValueString, 1.55 + const char * aPrependString, const char* aAppendString) 1.56 +{ 1.57 + if (aInputString && aInputString->EqualsLiteral("-moz-editor-invert-value")) { 1.58 + aOutputString.AssignLiteral("normal"); 1.59 + } 1.60 + else { 1.61 + aOutputString.AssignLiteral("bold"); 1.62 + } 1.63 +} 1.64 + 1.65 +static 1.66 +void ProcessDefaultValue(const nsAString * aInputString, nsAString & aOutputString, 1.67 + const char * aDefaultValueString, 1.68 + const char * aPrependString, const char* aAppendString) 1.69 +{ 1.70 + CopyASCIItoUTF16(aDefaultValueString, aOutputString); 1.71 +} 1.72 + 1.73 +static 1.74 +void ProcessSameValue(const nsAString * aInputString, nsAString & aOutputString, 1.75 + const char * aDefaultValueString, 1.76 + const char * aPrependString, const char* aAppendString) 1.77 +{ 1.78 + if (aInputString) { 1.79 + aOutputString.Assign(*aInputString); 1.80 + } 1.81 + else 1.82 + aOutputString.Truncate(); 1.83 +} 1.84 + 1.85 +static 1.86 +void ProcessExtendedValue(const nsAString * aInputString, nsAString & aOutputString, 1.87 + const char * aDefaultValueString, 1.88 + const char * aPrependString, const char* aAppendString) 1.89 +{ 1.90 + aOutputString.Truncate(); 1.91 + if (aInputString) { 1.92 + if (aPrependString) { 1.93 + AppendASCIItoUTF16(aPrependString, aOutputString); 1.94 + } 1.95 + aOutputString.Append(*aInputString); 1.96 + if (aAppendString) { 1.97 + AppendASCIItoUTF16(aAppendString, aOutputString); 1.98 + } 1.99 + } 1.100 +} 1.101 + 1.102 +static 1.103 +void ProcessLengthValue(const nsAString * aInputString, nsAString & aOutputString, 1.104 + const char * aDefaultValueString, 1.105 + const char * aPrependString, const char* aAppendString) 1.106 +{ 1.107 + aOutputString.Truncate(); 1.108 + if (aInputString) { 1.109 + aOutputString.Append(*aInputString); 1.110 + if (-1 == aOutputString.FindChar(char16_t('%'))) { 1.111 + aOutputString.AppendLiteral("px"); 1.112 + } 1.113 + } 1.114 +} 1.115 + 1.116 +static 1.117 +void ProcessListStyleTypeValue(const nsAString * aInputString, nsAString & aOutputString, 1.118 + const char * aDefaultValueString, 1.119 + const char * aPrependString, const char* aAppendString) 1.120 +{ 1.121 + aOutputString.Truncate(); 1.122 + if (aInputString) { 1.123 + if (aInputString->EqualsLiteral("1")) { 1.124 + aOutputString.AppendLiteral("decimal"); 1.125 + } 1.126 + else if (aInputString->EqualsLiteral("a")) { 1.127 + aOutputString.AppendLiteral("lower-alpha"); 1.128 + } 1.129 + else if (aInputString->EqualsLiteral("A")) { 1.130 + aOutputString.AppendLiteral("upper-alpha"); 1.131 + } 1.132 + else if (aInputString->EqualsLiteral("i")) { 1.133 + aOutputString.AppendLiteral("lower-roman"); 1.134 + } 1.135 + else if (aInputString->EqualsLiteral("I")) { 1.136 + aOutputString.AppendLiteral("upper-roman"); 1.137 + } 1.138 + else if (aInputString->EqualsLiteral("square") 1.139 + || aInputString->EqualsLiteral("circle") 1.140 + || aInputString->EqualsLiteral("disc")) { 1.141 + aOutputString.Append(*aInputString); 1.142 + } 1.143 + } 1.144 +} 1.145 + 1.146 +static 1.147 +void ProcessMarginLeftValue(const nsAString * aInputString, nsAString & aOutputString, 1.148 + const char * aDefaultValueString, 1.149 + const char * aPrependString, const char* aAppendString) 1.150 +{ 1.151 + aOutputString.Truncate(); 1.152 + if (aInputString) { 1.153 + if (aInputString->EqualsLiteral("center") || 1.154 + aInputString->EqualsLiteral("-moz-center")) { 1.155 + aOutputString.AppendLiteral("auto"); 1.156 + } 1.157 + else if (aInputString->EqualsLiteral("right") || 1.158 + aInputString->EqualsLiteral("-moz-right")) { 1.159 + aOutputString.AppendLiteral("auto"); 1.160 + } 1.161 + else { 1.162 + aOutputString.AppendLiteral("0px"); 1.163 + } 1.164 + } 1.165 +} 1.166 + 1.167 +static 1.168 +void ProcessMarginRightValue(const nsAString * aInputString, nsAString & aOutputString, 1.169 + const char * aDefaultValueString, 1.170 + const char * aPrependString, const char* aAppendString) 1.171 +{ 1.172 + aOutputString.Truncate(); 1.173 + if (aInputString) { 1.174 + if (aInputString->EqualsLiteral("center") || 1.175 + aInputString->EqualsLiteral("-moz-center")) { 1.176 + aOutputString.AppendLiteral("auto"); 1.177 + } 1.178 + else if (aInputString->EqualsLiteral("left") || 1.179 + aInputString->EqualsLiteral("-moz-left")) { 1.180 + aOutputString.AppendLiteral("auto"); 1.181 + } 1.182 + else { 1.183 + aOutputString.AppendLiteral("0px"); 1.184 + } 1.185 + } 1.186 +} 1.187 + 1.188 +const nsHTMLCSSUtils::CSSEquivTable boldEquivTable[] = { 1.189 + { nsHTMLCSSUtils::eCSSEditableProperty_font_weight, ProcessBValue, nullptr, nullptr, nullptr, true, false }, 1.190 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.191 +}; 1.192 + 1.193 +const nsHTMLCSSUtils::CSSEquivTable italicEquivTable[] = { 1.194 + { nsHTMLCSSUtils::eCSSEditableProperty_font_style, ProcessDefaultValue, "italic", nullptr, nullptr, true, false }, 1.195 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.196 +}; 1.197 + 1.198 +const nsHTMLCSSUtils::CSSEquivTable underlineEquivTable[] = { 1.199 + { nsHTMLCSSUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "underline", nullptr, nullptr, true, false }, 1.200 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.201 +}; 1.202 + 1.203 +const nsHTMLCSSUtils::CSSEquivTable strikeEquivTable[] = { 1.204 + { nsHTMLCSSUtils::eCSSEditableProperty_text_decoration, ProcessDefaultValue, "line-through", nullptr, nullptr, true, false }, 1.205 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.206 +}; 1.207 + 1.208 +const nsHTMLCSSUtils::CSSEquivTable ttEquivTable[] = { 1.209 + { nsHTMLCSSUtils::eCSSEditableProperty_font_family, ProcessDefaultValue, "monospace", nullptr, nullptr, true, false }, 1.210 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.211 +}; 1.212 + 1.213 +const nsHTMLCSSUtils::CSSEquivTable fontColorEquivTable[] = { 1.214 + { nsHTMLCSSUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, 1.215 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.216 +}; 1.217 + 1.218 +const nsHTMLCSSUtils::CSSEquivTable fontFaceEquivTable[] = { 1.219 + { nsHTMLCSSUtils::eCSSEditableProperty_font_family, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, 1.220 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.221 +}; 1.222 + 1.223 +const nsHTMLCSSUtils::CSSEquivTable bgcolorEquivTable[] = { 1.224 + { nsHTMLCSSUtils::eCSSEditableProperty_background_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, 1.225 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.226 +}; 1.227 + 1.228 +const nsHTMLCSSUtils::CSSEquivTable backgroundImageEquivTable[] = { 1.229 + { nsHTMLCSSUtils::eCSSEditableProperty_background_image, ProcessExtendedValue, nullptr, "url(", ")", true, true }, 1.230 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.231 +}; 1.232 + 1.233 +const nsHTMLCSSUtils::CSSEquivTable textColorEquivTable[] = { 1.234 + { nsHTMLCSSUtils::eCSSEditableProperty_color, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, 1.235 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.236 +}; 1.237 + 1.238 +const nsHTMLCSSUtils::CSSEquivTable borderEquivTable[] = { 1.239 + { nsHTMLCSSUtils::eCSSEditableProperty_border, ProcessExtendedValue, nullptr, nullptr, "px solid", true, false }, 1.240 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.241 +}; 1.242 + 1.243 +const nsHTMLCSSUtils::CSSEquivTable textAlignEquivTable[] = { 1.244 + { nsHTMLCSSUtils::eCSSEditableProperty_text_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, 1.245 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.246 +}; 1.247 + 1.248 +const nsHTMLCSSUtils::CSSEquivTable captionAlignEquivTable[] = { 1.249 + { nsHTMLCSSUtils::eCSSEditableProperty_caption_side, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, 1.250 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.251 +}; 1.252 + 1.253 +const nsHTMLCSSUtils::CSSEquivTable verticalAlignEquivTable[] = { 1.254 + { nsHTMLCSSUtils::eCSSEditableProperty_vertical_align, ProcessSameValue, nullptr, nullptr, nullptr, true, false }, 1.255 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.256 +}; 1.257 + 1.258 +const nsHTMLCSSUtils::CSSEquivTable nowrapEquivTable[] = { 1.259 + { nsHTMLCSSUtils::eCSSEditableProperty_whitespace, ProcessDefaultValue, "nowrap", nullptr, nullptr, true, false }, 1.260 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.261 +}; 1.262 + 1.263 +const nsHTMLCSSUtils::CSSEquivTable widthEquivTable[] = { 1.264 + { nsHTMLCSSUtils::eCSSEditableProperty_width, ProcessLengthValue, nullptr, nullptr, nullptr, true, false }, 1.265 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.266 +}; 1.267 + 1.268 +const nsHTMLCSSUtils::CSSEquivTable heightEquivTable[] = { 1.269 + { nsHTMLCSSUtils::eCSSEditableProperty_height, ProcessLengthValue, nullptr, nullptr, nullptr, true, false }, 1.270 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.271 +}; 1.272 + 1.273 +const nsHTMLCSSUtils::CSSEquivTable listStyleTypeEquivTable[] = { 1.274 + { nsHTMLCSSUtils::eCSSEditableProperty_list_style_type, ProcessListStyleTypeValue, nullptr, nullptr, nullptr, true, true }, 1.275 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.276 +}; 1.277 + 1.278 +const nsHTMLCSSUtils::CSSEquivTable tableAlignEquivTable[] = { 1.279 + { nsHTMLCSSUtils::eCSSEditableProperty_text_align, ProcessDefaultValue, "left", nullptr, nullptr, false, false }, 1.280 + { nsHTMLCSSUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false }, 1.281 + { nsHTMLCSSUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false }, 1.282 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.283 +}; 1.284 + 1.285 +const nsHTMLCSSUtils::CSSEquivTable hrAlignEquivTable[] = { 1.286 + { nsHTMLCSSUtils::eCSSEditableProperty_margin_left, ProcessMarginLeftValue, nullptr, nullptr, nullptr, true, false }, 1.287 + { nsHTMLCSSUtils::eCSSEditableProperty_margin_right, ProcessMarginRightValue, nullptr, nullptr, nullptr, true, false }, 1.288 + { nsHTMLCSSUtils::eCSSEditableProperty_NONE, 0 } 1.289 +}; 1.290 + 1.291 +nsHTMLCSSUtils::nsHTMLCSSUtils(nsHTMLEditor* aEditor) 1.292 + : mHTMLEditor(aEditor) 1.293 + , mIsCSSPrefChecked(true) 1.294 +{ 1.295 + // let's retrieve the value of the "CSS editing" pref 1.296 + mIsCSSPrefChecked = Preferences::GetBool("editor.use_css", mIsCSSPrefChecked); 1.297 +} 1.298 + 1.299 +nsHTMLCSSUtils::~nsHTMLCSSUtils() 1.300 +{ 1.301 +} 1.302 + 1.303 +// Answers true if we have some CSS equivalence for the HTML style defined 1.304 +// by aProperty and/or aAttribute for the node aNode 1.305 +bool 1.306 +nsHTMLCSSUtils::IsCSSEditableProperty(nsIDOMNode* aNode, 1.307 + nsIAtom* aProperty, 1.308 + const nsAString* aAttribute) 1.309 +{ 1.310 + NS_ASSERTION(aNode, "Shouldn't you pass aNode? - Bug 214025"); 1.311 + 1.312 + nsCOMPtr<nsIContent> content = do_QueryInterface(aNode); 1.313 + NS_ENSURE_TRUE(content, false); 1.314 + return IsCSSEditableProperty(content, aProperty, aAttribute); 1.315 +} 1.316 + 1.317 +bool 1.318 +nsHTMLCSSUtils::IsCSSEditableProperty(nsIContent* aNode, 1.319 + nsIAtom* aProperty, 1.320 + const nsAString* aAttribute) 1.321 +{ 1.322 + MOZ_ASSERT(aNode); 1.323 + 1.324 + nsIContent* content = aNode; 1.325 + // we need an element node here 1.326 + if (content->NodeType() == nsIDOMNode::TEXT_NODE) { 1.327 + content = content->GetParent(); 1.328 + NS_ENSURE_TRUE(content, false); 1.329 + } 1.330 + 1.331 + nsIAtom *tagName = content->Tag(); 1.332 + // brade: shouldn't some of the above go below the next block? 1.333 + 1.334 + // html inline styles B I TT U STRIKE and COLOR/FACE on FONT 1.335 + if (nsEditProperty::b == aProperty 1.336 + || nsEditProperty::i == aProperty 1.337 + || nsEditProperty::tt == aProperty 1.338 + || nsEditProperty::u == aProperty 1.339 + || nsEditProperty::strike == aProperty 1.340 + || ((nsEditProperty::font == aProperty) && aAttribute && 1.341 + (aAttribute->EqualsLiteral("color") || 1.342 + aAttribute->EqualsLiteral("face")))) { 1.343 + return true; 1.344 + } 1.345 + 1.346 + // ALIGN attribute on elements supporting it 1.347 + if (aAttribute && (aAttribute->EqualsLiteral("align")) && 1.348 + (nsEditProperty::div == tagName 1.349 + || nsEditProperty::p == tagName 1.350 + || nsEditProperty::h1 == tagName 1.351 + || nsEditProperty::h2 == tagName 1.352 + || nsEditProperty::h3 == tagName 1.353 + || nsEditProperty::h4 == tagName 1.354 + || nsEditProperty::h5 == tagName 1.355 + || nsEditProperty::h6 == tagName 1.356 + || nsEditProperty::td == tagName 1.357 + || nsEditProperty::th == tagName 1.358 + || nsEditProperty::table == tagName 1.359 + || nsEditProperty::hr == tagName 1.360 + // brade: for the above, why not use nsHTMLEditUtils::SupportsAlignAttr 1.361 + // brade: but it also checks for tbody, tfoot, thead 1.362 + // Let's add the following elements here even if ALIGN has not 1.363 + // the same meaning for them 1.364 + || nsEditProperty::legend == tagName 1.365 + || nsEditProperty::caption == tagName)) { 1.366 + return true; 1.367 + } 1.368 + 1.369 + if (aAttribute && (aAttribute->EqualsLiteral("valign")) && 1.370 + (nsEditProperty::col == tagName 1.371 + || nsEditProperty::colgroup == tagName 1.372 + || nsEditProperty::tbody == tagName 1.373 + || nsEditProperty::td == tagName 1.374 + || nsEditProperty::th == tagName 1.375 + || nsEditProperty::tfoot == tagName 1.376 + || nsEditProperty::thead == tagName 1.377 + || nsEditProperty::tr == tagName)) { 1.378 + return true; 1.379 + } 1.380 + 1.381 + // attributes TEXT, BACKGROUND and BGCOLOR on BODY 1.382 + if (aAttribute && (nsEditProperty::body == tagName) && 1.383 + (aAttribute->EqualsLiteral("text") 1.384 + || aAttribute->EqualsLiteral("background") 1.385 + || aAttribute->EqualsLiteral("bgcolor"))) { 1.386 + return true; 1.387 + } 1.388 + 1.389 + // attribute BGCOLOR on other elements 1.390 + if (aAttribute && aAttribute->EqualsLiteral("bgcolor")) { 1.391 + return true; 1.392 + } 1.393 + 1.394 + // attributes HEIGHT, WIDTH and NOWRAP on TD and TH 1.395 + if (aAttribute && ((nsEditProperty::td == tagName) 1.396 + || (nsEditProperty::th == tagName)) && 1.397 + (aAttribute->EqualsLiteral("height") 1.398 + || aAttribute->EqualsLiteral("width") 1.399 + || aAttribute->EqualsLiteral("nowrap"))) { 1.400 + return true; 1.401 + } 1.402 + 1.403 + // attributes HEIGHT and WIDTH on TABLE 1.404 + if (aAttribute && (nsEditProperty::table == tagName) && 1.405 + (aAttribute->EqualsLiteral("height") 1.406 + || aAttribute->EqualsLiteral("width"))) { 1.407 + return true; 1.408 + } 1.409 + 1.410 + // attributes SIZE and WIDTH on HR 1.411 + if (aAttribute && (nsEditProperty::hr == tagName) && 1.412 + (aAttribute->EqualsLiteral("size") 1.413 + || aAttribute->EqualsLiteral("width"))) { 1.414 + return true; 1.415 + } 1.416 + 1.417 + // attribute TYPE on OL UL LI 1.418 + if (aAttribute && (nsEditProperty::ol == tagName 1.419 + || nsEditProperty::ul == tagName 1.420 + || nsEditProperty::li == tagName) && 1.421 + aAttribute->EqualsLiteral("type")) { 1.422 + return true; 1.423 + } 1.424 + 1.425 + if (aAttribute && nsEditProperty::img == tagName && 1.426 + (aAttribute->EqualsLiteral("border") 1.427 + || aAttribute->EqualsLiteral("width") 1.428 + || aAttribute->EqualsLiteral("height"))) { 1.429 + return true; 1.430 + } 1.431 + 1.432 + // other elements that we can align using CSS even if they 1.433 + // can't carry the html ALIGN attribute 1.434 + if (aAttribute && aAttribute->EqualsLiteral("align") && 1.435 + (nsEditProperty::ul == tagName 1.436 + || nsEditProperty::ol == tagName 1.437 + || nsEditProperty::dl == tagName 1.438 + || nsEditProperty::li == tagName 1.439 + || nsEditProperty::dd == tagName 1.440 + || nsEditProperty::dt == tagName 1.441 + || nsEditProperty::address == tagName 1.442 + || nsEditProperty::pre == tagName 1.443 + || nsEditProperty::ul == tagName)) { 1.444 + return true; 1.445 + } 1.446 + 1.447 + return false; 1.448 +} 1.449 + 1.450 +// the lowest level above the transaction; adds the css declaration "aProperty : aValue" to 1.451 +// the inline styles carried by aElement 1.452 +nsresult 1.453 +nsHTMLCSSUtils::SetCSSProperty(nsIDOMElement *aElement, nsIAtom * aProperty, const nsAString & aValue, 1.454 + bool aSuppressTransaction) 1.455 +{ 1.456 + nsRefPtr<ChangeCSSInlineStyleTxn> txn; 1.457 + nsresult result = CreateCSSPropertyTxn(aElement, aProperty, aValue, 1.458 + getter_AddRefs(txn), false); 1.459 + if (NS_SUCCEEDED(result)) { 1.460 + if (aSuppressTransaction) { 1.461 + result = txn->DoTransaction(); 1.462 + } 1.463 + else { 1.464 + result = mHTMLEditor->DoTransaction(txn); 1.465 + } 1.466 + } 1.467 + return result; 1.468 +} 1.469 + 1.470 +nsresult 1.471 +nsHTMLCSSUtils::SetCSSPropertyPixels(nsIDOMElement *aElement, 1.472 + nsIAtom *aProperty, 1.473 + int32_t aIntValue, 1.474 + bool aSuppressTransaction) 1.475 +{ 1.476 + nsAutoString s; 1.477 + s.AppendInt(aIntValue); 1.478 + return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px"), 1.479 + aSuppressTransaction); 1.480 +} 1.481 + 1.482 +// the lowest level above the transaction; removes the value aValue from the list of values 1.483 +// specified for the CSS property aProperty, or totally remove the declaration if this 1.484 +// property accepts only one value 1.485 +nsresult 1.486 +nsHTMLCSSUtils::RemoveCSSProperty(nsIDOMElement *aElement, nsIAtom * aProperty, const nsAString & aValue, 1.487 + bool aSuppressTransaction) 1.488 +{ 1.489 + nsRefPtr<ChangeCSSInlineStyleTxn> txn; 1.490 + nsresult result = CreateCSSPropertyTxn(aElement, aProperty, aValue, 1.491 + getter_AddRefs(txn), true); 1.492 + if (NS_SUCCEEDED(result)) { 1.493 + if (aSuppressTransaction) { 1.494 + result = txn->DoTransaction(); 1.495 + } 1.496 + else { 1.497 + result = mHTMLEditor->DoTransaction(txn); 1.498 + } 1.499 + } 1.500 + return result; 1.501 +} 1.502 + 1.503 +nsresult 1.504 +nsHTMLCSSUtils::CreateCSSPropertyTxn(nsIDOMElement *aElement, 1.505 + nsIAtom * aAttribute, 1.506 + const nsAString& aValue, 1.507 + ChangeCSSInlineStyleTxn ** aTxn, 1.508 + bool aRemoveProperty) 1.509 +{ 1.510 + NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER); 1.511 + 1.512 + *aTxn = new ChangeCSSInlineStyleTxn(); 1.513 + NS_ENSURE_TRUE(*aTxn, NS_ERROR_OUT_OF_MEMORY); 1.514 + NS_ADDREF(*aTxn); 1.515 + return (*aTxn)->Init(mHTMLEditor, aElement, aAttribute, aValue, aRemoveProperty); 1.516 +} 1.517 + 1.518 +nsresult 1.519 +nsHTMLCSSUtils::GetSpecifiedProperty(nsIDOMNode *aNode, nsIAtom *aProperty, 1.520 + nsAString & aValue) 1.521 +{ 1.522 + return GetCSSInlinePropertyBase(aNode, aProperty, aValue, eSpecified); 1.523 +} 1.524 + 1.525 +nsresult 1.526 +nsHTMLCSSUtils::GetComputedProperty(nsIDOMNode *aNode, nsIAtom *aProperty, 1.527 + nsAString & aValue) 1.528 +{ 1.529 + return GetCSSInlinePropertyBase(aNode, aProperty, aValue, eComputed); 1.530 +} 1.531 + 1.532 +nsresult 1.533 +nsHTMLCSSUtils::GetCSSInlinePropertyBase(nsIDOMNode* aNode, nsIAtom* aProperty, 1.534 + nsAString& aValue, 1.535 + StyleType aStyleType) 1.536 +{ 1.537 + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); 1.538 + return GetCSSInlinePropertyBase(node, aProperty, aValue, aStyleType); 1.539 +} 1.540 + 1.541 +nsresult 1.542 +nsHTMLCSSUtils::GetCSSInlinePropertyBase(nsINode* aNode, nsIAtom* aProperty, 1.543 + nsAString& aValue, 1.544 + StyleType aStyleType) 1.545 +{ 1.546 + MOZ_ASSERT(aNode && aProperty); 1.547 + aValue.Truncate(); 1.548 + 1.549 + nsCOMPtr<dom::Element> element = GetElementContainerOrSelf(aNode); 1.550 + NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER); 1.551 + 1.552 + if (aStyleType == eComputed) { 1.553 + // Get the all the computed css styles attached to the element node 1.554 + nsRefPtr<nsComputedDOMStyle> cssDecl = GetComputedStyle(element); 1.555 + NS_ENSURE_STATE(cssDecl); 1.556 + 1.557 + // from these declarations, get the one we want and that one only 1.558 + MOZ_ALWAYS_TRUE(NS_SUCCEEDED( 1.559 + cssDecl->GetPropertyValue(nsDependentAtomString(aProperty), aValue))); 1.560 + 1.561 + return NS_OK; 1.562 + } 1.563 + 1.564 + MOZ_ASSERT(aStyleType == eSpecified); 1.565 + nsRefPtr<css::StyleRule> rule = element->GetInlineStyleRule(); 1.566 + if (!rule) { 1.567 + return NS_OK; 1.568 + } 1.569 + nsCSSProperty prop = 1.570 + nsCSSProps::LookupProperty(nsDependentAtomString(aProperty), 1.571 + nsCSSProps::eEnabledForAllContent); 1.572 + MOZ_ASSERT(prop != eCSSProperty_UNKNOWN); 1.573 + rule->GetDeclaration()->GetValue(prop, aValue); 1.574 + 1.575 + return NS_OK; 1.576 +} 1.577 + 1.578 +already_AddRefed<nsComputedDOMStyle> 1.579 +nsHTMLCSSUtils::GetComputedStyle(nsIDOMElement* aElement) 1.580 +{ 1.581 + nsCOMPtr<dom::Element> element = do_QueryInterface(aElement); 1.582 + return GetComputedStyle(element); 1.583 +} 1.584 + 1.585 +already_AddRefed<nsComputedDOMStyle> 1.586 +nsHTMLCSSUtils::GetComputedStyle(dom::Element* aElement) 1.587 +{ 1.588 + MOZ_ASSERT(aElement); 1.589 + 1.590 + nsIDocument* doc = aElement->GetCurrentDoc(); 1.591 + NS_ENSURE_TRUE(doc, nullptr); 1.592 + 1.593 + nsIPresShell* presShell = doc->GetShell(); 1.594 + NS_ENSURE_TRUE(presShell, nullptr); 1.595 + 1.596 + nsRefPtr<nsComputedDOMStyle> style = 1.597 + NS_NewComputedDOMStyle(aElement, EmptyString(), presShell); 1.598 + 1.599 + return style.forget(); 1.600 +} 1.601 + 1.602 +// remove the CSS style "aProperty : aPropertyValue" and possibly remove the whole node 1.603 +// if it is a span and if its only attribute is _moz_dirty 1.604 +nsresult 1.605 +nsHTMLCSSUtils::RemoveCSSInlineStyle(nsIDOMNode *aNode, nsIAtom *aProperty, const nsAString & aPropertyValue) 1.606 +{ 1.607 + nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(aNode); 1.608 + 1.609 + // remove the property from the style attribute 1.610 + nsresult res = RemoveCSSProperty(elem, aProperty, aPropertyValue, false); 1.611 + NS_ENSURE_SUCCESS(res, res); 1.612 + 1.613 + nsCOMPtr<dom::Element> element = do_QueryInterface(aNode); 1.614 + if (!element || !element->IsHTML(nsGkAtoms::span) || 1.615 + nsHTMLEditor::HasAttributes(element)) { 1.616 + return NS_OK; 1.617 + } 1.618 + 1.619 + return mHTMLEditor->RemoveContainer(aNode); 1.620 +} 1.621 + 1.622 +// Answers true is the property can be removed by setting a "none" CSS value 1.623 +// on a node 1.624 +bool 1.625 +nsHTMLCSSUtils::IsCSSInvertable(nsIAtom *aProperty, const nsAString *aAttribute) 1.626 +{ 1.627 + return bool(nsEditProperty::b == aProperty); 1.628 +} 1.629 + 1.630 +// Get the default browser background color if we need it for GetCSSBackgroundColorState 1.631 +void 1.632 +nsHTMLCSSUtils::GetDefaultBackgroundColor(nsAString & aColor) 1.633 +{ 1.634 + if (Preferences::GetBool("editor.use_custom_colors", false)) { 1.635 + nsresult rv = Preferences::GetString("editor.background_color", &aColor); 1.636 + // XXX Why don't you validate the pref value? 1.637 + if (NS_FAILED(rv)) { 1.638 + NS_WARNING("failed to get editor.background_color"); 1.639 + aColor.AssignLiteral("#ffffff"); // Default to white 1.640 + } 1.641 + return; 1.642 + } 1.643 + 1.644 + if (Preferences::GetBool("browser.display.use_system_colors", false)) { 1.645 + return; 1.646 + } 1.647 + 1.648 + nsresult rv = 1.649 + Preferences::GetString("browser.display.background_color", &aColor); 1.650 + // XXX Why don't you validate the pref value? 1.651 + if (NS_FAILED(rv)) { 1.652 + NS_WARNING("failed to get browser.display.background_color"); 1.653 + aColor.AssignLiteral("#ffffff"); // Default to white 1.654 + } 1.655 +} 1.656 + 1.657 +// Get the default length unit used for CSS Indent/Outdent 1.658 +void 1.659 +nsHTMLCSSUtils::GetDefaultLengthUnit(nsAString & aLengthUnit) 1.660 +{ 1.661 + nsresult rv = 1.662 + Preferences::GetString("editor.css.default_length_unit", &aLengthUnit); 1.663 + // XXX Why don't you validate the pref value? 1.664 + if (NS_FAILED(rv)) { 1.665 + aLengthUnit.AssignLiteral("px"); 1.666 + } 1.667 +} 1.668 + 1.669 +// Unfortunately, CSSStyleDeclaration::GetPropertyCSSValue is not yet implemented... 1.670 +// We need then a way to determine the number part and the unit from aString, aString 1.671 +// being the result of a GetPropertyValue query... 1.672 +void 1.673 +nsHTMLCSSUtils::ParseLength(const nsAString & aString, float * aValue, nsIAtom ** aUnit) 1.674 +{ 1.675 + nsAString::const_iterator iter; 1.676 + aString.BeginReading(iter); 1.677 + 1.678 + float a = 10.0f , b = 1.0f, value = 0; 1.679 + int8_t sign = 1; 1.680 + int32_t i = 0, j = aString.Length(); 1.681 + char16_t c; 1.682 + bool floatingPointFound = false; 1.683 + c = *iter; 1.684 + if (char16_t('-') == c) { sign = -1; iter++; i++; } 1.685 + else if (char16_t('+') == c) { iter++; i++; } 1.686 + while (i < j) { 1.687 + c = *iter; 1.688 + if ((char16_t('0') == c) || 1.689 + (char16_t('1') == c) || 1.690 + (char16_t('2') == c) || 1.691 + (char16_t('3') == c) || 1.692 + (char16_t('4') == c) || 1.693 + (char16_t('5') == c) || 1.694 + (char16_t('6') == c) || 1.695 + (char16_t('7') == c) || 1.696 + (char16_t('8') == c) || 1.697 + (char16_t('9') == c)) { 1.698 + value = (value * a) + (b * (c - char16_t('0'))); 1.699 + b = b / 10 * a; 1.700 + } 1.701 + else if (!floatingPointFound && (char16_t('.') == c)) { 1.702 + floatingPointFound = true; 1.703 + a = 1.0f; b = 0.1f; 1.704 + } 1.705 + else break; 1.706 + iter++; 1.707 + i++; 1.708 + } 1.709 + *aValue = value * sign; 1.710 + *aUnit = NS_NewAtom(StringTail(aString, j-i)).take(); 1.711 +} 1.712 + 1.713 +void 1.714 +nsHTMLCSSUtils::GetCSSPropertyAtom(nsCSSEditableProperty aProperty, nsIAtom ** aAtom) 1.715 +{ 1.716 + *aAtom = nullptr; 1.717 + switch (aProperty) { 1.718 + case eCSSEditableProperty_background_color: 1.719 + *aAtom = nsEditProperty::cssBackgroundColor; 1.720 + break; 1.721 + case eCSSEditableProperty_background_image: 1.722 + *aAtom = nsEditProperty::cssBackgroundImage; 1.723 + break; 1.724 + case eCSSEditableProperty_border: 1.725 + *aAtom = nsEditProperty::cssBorder; 1.726 + break; 1.727 + case eCSSEditableProperty_caption_side: 1.728 + *aAtom = nsEditProperty::cssCaptionSide; 1.729 + break; 1.730 + case eCSSEditableProperty_color: 1.731 + *aAtom = nsEditProperty::cssColor; 1.732 + break; 1.733 + case eCSSEditableProperty_float: 1.734 + *aAtom = nsEditProperty::cssFloat; 1.735 + break; 1.736 + case eCSSEditableProperty_font_family: 1.737 + *aAtom = nsEditProperty::cssFontFamily; 1.738 + break; 1.739 + case eCSSEditableProperty_font_size: 1.740 + *aAtom = nsEditProperty::cssFontSize; 1.741 + break; 1.742 + case eCSSEditableProperty_font_style: 1.743 + *aAtom = nsEditProperty::cssFontStyle; 1.744 + break; 1.745 + case eCSSEditableProperty_font_weight: 1.746 + *aAtom = nsEditProperty::cssFontWeight; 1.747 + break; 1.748 + case eCSSEditableProperty_height: 1.749 + *aAtom = nsEditProperty::cssHeight; 1.750 + break; 1.751 + case eCSSEditableProperty_list_style_type: 1.752 + *aAtom = nsEditProperty::cssListStyleType; 1.753 + break; 1.754 + case eCSSEditableProperty_margin_left: 1.755 + *aAtom = nsEditProperty::cssMarginLeft; 1.756 + break; 1.757 + case eCSSEditableProperty_margin_right: 1.758 + *aAtom = nsEditProperty::cssMarginRight; 1.759 + break; 1.760 + case eCSSEditableProperty_text_align: 1.761 + *aAtom = nsEditProperty::cssTextAlign; 1.762 + break; 1.763 + case eCSSEditableProperty_text_decoration: 1.764 + *aAtom = nsEditProperty::cssTextDecoration; 1.765 + break; 1.766 + case eCSSEditableProperty_vertical_align: 1.767 + *aAtom = nsEditProperty::cssVerticalAlign; 1.768 + break; 1.769 + case eCSSEditableProperty_whitespace: 1.770 + *aAtom = nsEditProperty::cssWhitespace; 1.771 + break; 1.772 + case eCSSEditableProperty_width: 1.773 + *aAtom = nsEditProperty::cssWidth; 1.774 + break; 1.775 + case eCSSEditableProperty_NONE: 1.776 + // intentionally empty 1.777 + break; 1.778 + } 1.779 +} 1.780 + 1.781 +// Populate aProperty and aValueArray with the CSS declarations equivalent to the 1.782 +// value aValue according to the equivalence table aEquivTable 1.783 +void 1.784 +nsHTMLCSSUtils::BuildCSSDeclarations(nsTArray<nsIAtom*> & aPropertyArray, 1.785 + nsTArray<nsString> & aValueArray, 1.786 + const CSSEquivTable * aEquivTable, 1.787 + const nsAString * aValue, 1.788 + bool aGetOrRemoveRequest) 1.789 +{ 1.790 + // clear arrays 1.791 + aPropertyArray.Clear(); 1.792 + aValueArray.Clear(); 1.793 + 1.794 + // if we have an input value, let's use it 1.795 + nsAutoString value, lowerCasedValue; 1.796 + if (aValue) { 1.797 + value.Assign(*aValue); 1.798 + lowerCasedValue.Assign(*aValue); 1.799 + ToLowerCase(lowerCasedValue); 1.800 + } 1.801 + 1.802 + int8_t index = 0; 1.803 + nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty; 1.804 + while (cssProperty) { 1.805 + if (!aGetOrRemoveRequest|| aEquivTable[index].gettable) { 1.806 + nsAutoString cssValue, cssPropertyString; 1.807 + nsIAtom * cssPropertyAtom; 1.808 + // find the equivalent css value for the index-th property in 1.809 + // the equivalence table 1.810 + (*aEquivTable[index].processValueFunctor) ((!aGetOrRemoveRequest || aEquivTable[index].caseSensitiveValue) ? &value : &lowerCasedValue, 1.811 + cssValue, 1.812 + aEquivTable[index].defaultValue, 1.813 + aEquivTable[index].prependValue, 1.814 + aEquivTable[index].appendValue); 1.815 + GetCSSPropertyAtom(cssProperty, &cssPropertyAtom); 1.816 + aPropertyArray.AppendElement(cssPropertyAtom); 1.817 + aValueArray.AppendElement(cssValue); 1.818 + } 1.819 + index++; 1.820 + cssProperty = aEquivTable[index].cssProperty; 1.821 + } 1.822 +} 1.823 + 1.824 +// Populate cssPropertyArray and cssValueArray with the declarations equivalent 1.825 +// to aHTMLProperty/aAttribute/aValue for the node aNode 1.826 +void 1.827 +nsHTMLCSSUtils::GenerateCSSDeclarationsFromHTMLStyle(dom::Element* aElement, 1.828 + nsIAtom* aHTMLProperty, 1.829 + const nsAString* aAttribute, 1.830 + const nsAString* aValue, 1.831 + nsTArray<nsIAtom*>& cssPropertyArray, 1.832 + nsTArray<nsString>& cssValueArray, 1.833 + bool aGetOrRemoveRequest) 1.834 +{ 1.835 + MOZ_ASSERT(aElement); 1.836 + nsIAtom* tagName = aElement->Tag(); 1.837 + const nsHTMLCSSUtils::CSSEquivTable* equivTable = nullptr; 1.838 + 1.839 + if (nsEditProperty::b == aHTMLProperty) { 1.840 + equivTable = boldEquivTable; 1.841 + } else if (nsEditProperty::i == aHTMLProperty) { 1.842 + equivTable = italicEquivTable; 1.843 + } else if (nsEditProperty::u == aHTMLProperty) { 1.844 + equivTable = underlineEquivTable; 1.845 + } else if (nsEditProperty::strike == aHTMLProperty) { 1.846 + equivTable = strikeEquivTable; 1.847 + } else if (nsEditProperty::tt == aHTMLProperty) { 1.848 + equivTable = ttEquivTable; 1.849 + } else if (aAttribute) { 1.850 + if (nsEditProperty::font == aHTMLProperty && 1.851 + aAttribute->EqualsLiteral("color")) { 1.852 + equivTable = fontColorEquivTable; 1.853 + } else if (nsEditProperty::font == aHTMLProperty && 1.854 + aAttribute->EqualsLiteral("face")) { 1.855 + equivTable = fontFaceEquivTable; 1.856 + } else if (aAttribute->EqualsLiteral("bgcolor")) { 1.857 + equivTable = bgcolorEquivTable; 1.858 + } else if (aAttribute->EqualsLiteral("background")) { 1.859 + equivTable = backgroundImageEquivTable; 1.860 + } else if (aAttribute->EqualsLiteral("text")) { 1.861 + equivTable = textColorEquivTable; 1.862 + } else if (aAttribute->EqualsLiteral("border")) { 1.863 + equivTable = borderEquivTable; 1.864 + } else if (aAttribute->EqualsLiteral("align")) { 1.865 + if (nsEditProperty::table == tagName) { 1.866 + equivTable = tableAlignEquivTable; 1.867 + } else if (nsEditProperty::hr == tagName) { 1.868 + equivTable = hrAlignEquivTable; 1.869 + } else if (nsEditProperty::legend == tagName || 1.870 + nsEditProperty::caption == tagName) { 1.871 + equivTable = captionAlignEquivTable; 1.872 + } else { 1.873 + equivTable = textAlignEquivTable; 1.874 + } 1.875 + } else if (aAttribute->EqualsLiteral("valign")) { 1.876 + equivTable = verticalAlignEquivTable; 1.877 + } else if (aAttribute->EqualsLiteral("nowrap")) { 1.878 + equivTable = nowrapEquivTable; 1.879 + } else if (aAttribute->EqualsLiteral("width")) { 1.880 + equivTable = widthEquivTable; 1.881 + } else if (aAttribute->EqualsLiteral("height") || 1.882 + (nsEditProperty::hr == tagName && 1.883 + aAttribute->EqualsLiteral("size"))) { 1.884 + equivTable = heightEquivTable; 1.885 + } else if (aAttribute->EqualsLiteral("type") && 1.886 + (nsEditProperty::ol == tagName 1.887 + || nsEditProperty::ul == tagName 1.888 + || nsEditProperty::li == tagName)) { 1.889 + equivTable = listStyleTypeEquivTable; 1.890 + } 1.891 + } 1.892 + if (equivTable) { 1.893 + BuildCSSDeclarations(cssPropertyArray, cssValueArray, equivTable, 1.894 + aValue, aGetOrRemoveRequest); 1.895 + } 1.896 +} 1.897 + 1.898 +// Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/ 1.899 +// aValue for the node, and return in aCount the number of CSS properties set 1.900 +// by the call. The dom::Element version returns aCount instead. 1.901 +int32_t 1.902 +nsHTMLCSSUtils::SetCSSEquivalentToHTMLStyle(dom::Element* aElement, 1.903 + nsIAtom* aProperty, 1.904 + const nsAString* aAttribute, 1.905 + const nsAString* aValue, 1.906 + bool aSuppressTransaction) 1.907 +{ 1.908 + MOZ_ASSERT(aElement && aProperty); 1.909 + MOZ_ASSERT_IF(aAttribute, aValue); 1.910 + int32_t count; 1.911 + // This can only fail if SetCSSProperty fails, which should only happen if 1.912 + // something is pretty badly wrong. In this case we assert so that hopefully 1.913 + // someone will notice, but there's nothing more sensible to do than just 1.914 + // return the count and carry on. 1.915 + nsresult res = SetCSSEquivalentToHTMLStyle(aElement->AsDOMNode(), 1.916 + aProperty, aAttribute, 1.917 + aValue, &count, 1.918 + aSuppressTransaction); 1.919 + NS_ASSERTION(NS_SUCCEEDED(res), "SetCSSEquivalentToHTMLStyle failed"); 1.920 + NS_ENSURE_SUCCESS(res, count); 1.921 + return count; 1.922 +} 1.923 + 1.924 +nsresult 1.925 +nsHTMLCSSUtils::SetCSSEquivalentToHTMLStyle(nsIDOMNode * aNode, 1.926 + nsIAtom *aHTMLProperty, 1.927 + const nsAString *aAttribute, 1.928 + const nsAString *aValue, 1.929 + int32_t * aCount, 1.930 + bool aSuppressTransaction) 1.931 +{ 1.932 + nsCOMPtr<dom::Element> element = do_QueryInterface(aNode); 1.933 + *aCount = 0; 1.934 + if (!element || !IsCSSEditableProperty(element, aHTMLProperty, aAttribute)) { 1.935 + return NS_OK; 1.936 + } 1.937 + 1.938 + // we can apply the styles only if the node is an element and if we have 1.939 + // an equivalence for the requested HTML style in this implementation 1.940 + 1.941 + // Find the CSS equivalence to the HTML style 1.942 + nsTArray<nsIAtom*> cssPropertyArray; 1.943 + nsTArray<nsString> cssValueArray; 1.944 + GenerateCSSDeclarationsFromHTMLStyle(element, aHTMLProperty, aAttribute, 1.945 + aValue, cssPropertyArray, cssValueArray, 1.946 + false); 1.947 + 1.948 + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(element); 1.949 + // set the individual CSS inline styles 1.950 + *aCount = cssPropertyArray.Length(); 1.951 + for (int32_t index = 0; index < *aCount; index++) { 1.952 + nsresult res = SetCSSProperty(domElement, cssPropertyArray[index], 1.953 + cssValueArray[index], aSuppressTransaction); 1.954 + NS_ENSURE_SUCCESS(res, res); 1.955 + } 1.956 + return NS_OK; 1.957 +} 1.958 + 1.959 +// Remove from aNode the CSS inline style equivalent to HTMLProperty/aAttribute/aValue for the node 1.960 +nsresult 1.961 +nsHTMLCSSUtils::RemoveCSSEquivalentToHTMLStyle(nsIDOMNode * aNode, 1.962 + nsIAtom *aHTMLProperty, 1.963 + const nsAString *aAttribute, 1.964 + const nsAString *aValue, 1.965 + bool aSuppressTransaction) 1.966 +{ 1.967 + nsCOMPtr<dom::Element> element = do_QueryInterface(aNode); 1.968 + NS_ENSURE_TRUE(element, NS_OK); 1.969 + 1.970 + return RemoveCSSEquivalentToHTMLStyle(element, aHTMLProperty, aAttribute, 1.971 + aValue, aSuppressTransaction); 1.972 +} 1.973 + 1.974 +nsresult 1.975 +nsHTMLCSSUtils::RemoveCSSEquivalentToHTMLStyle(dom::Element* aElement, 1.976 + nsIAtom* aHTMLProperty, 1.977 + const nsAString* aAttribute, 1.978 + const nsAString* aValue, 1.979 + bool aSuppressTransaction) 1.980 +{ 1.981 + MOZ_ASSERT(aElement); 1.982 + 1.983 + if (!IsCSSEditableProperty(aElement, aHTMLProperty, aAttribute)) { 1.984 + return NS_OK; 1.985 + } 1.986 + 1.987 + // we can apply the styles only if the node is an element and if we have 1.988 + // an equivalence for the requested HTML style in this implementation 1.989 + 1.990 + // Find the CSS equivalence to the HTML style 1.991 + nsTArray<nsIAtom*> cssPropertyArray; 1.992 + nsTArray<nsString> cssValueArray; 1.993 + GenerateCSSDeclarationsFromHTMLStyle(aElement, aHTMLProperty, aAttribute, 1.994 + aValue, cssPropertyArray, cssValueArray, 1.995 + true); 1.996 + 1.997 + nsCOMPtr<nsIDOMElement> domElement = do_QueryInterface(aElement); 1.998 + // remove the individual CSS inline styles 1.999 + int32_t count = cssPropertyArray.Length(); 1.1000 + for (int32_t index = 0; index < count; index++) { 1.1001 + nsresult res = RemoveCSSProperty(domElement, 1.1002 + cssPropertyArray[index], 1.1003 + cssValueArray[index], 1.1004 + aSuppressTransaction); 1.1005 + NS_ENSURE_SUCCESS(res, res); 1.1006 + } 1.1007 + return NS_OK; 1.1008 +} 1.1009 + 1.1010 +// returns in aValueString the list of values for the CSS equivalences to 1.1011 +// the HTML style aHTMLProperty/aAttribute/aValueString for the node aNode; 1.1012 +// the value of aStyleType controls the styles we retrieve : specified or 1.1013 +// computed. 1.1014 +nsresult 1.1015 +nsHTMLCSSUtils::GetCSSEquivalentToHTMLInlineStyleSet(nsINode* aNode, 1.1016 + nsIAtom *aHTMLProperty, 1.1017 + const nsAString *aAttribute, 1.1018 + nsAString & aValueString, 1.1019 + StyleType aStyleType) 1.1020 +{ 1.1021 + aValueString.Truncate(); 1.1022 + nsCOMPtr<dom::Element> theElement = GetElementContainerOrSelf(aNode); 1.1023 + NS_ENSURE_TRUE(theElement, NS_ERROR_NULL_POINTER); 1.1024 + 1.1025 + if (!theElement || !IsCSSEditableProperty(theElement, aHTMLProperty, aAttribute)) { 1.1026 + return NS_OK; 1.1027 + } 1.1028 + 1.1029 + // Yes, the requested HTML style has a CSS equivalence in this implementation 1.1030 + nsTArray<nsIAtom*> cssPropertyArray; 1.1031 + nsTArray<nsString> cssValueArray; 1.1032 + // get the CSS equivalence with last param true indicating we want only the 1.1033 + // "gettable" properties 1.1034 + GenerateCSSDeclarationsFromHTMLStyle(theElement, aHTMLProperty, aAttribute, nullptr, 1.1035 + cssPropertyArray, cssValueArray, true); 1.1036 + int32_t count = cssPropertyArray.Length(); 1.1037 + for (int32_t index = 0; index < count; index++) { 1.1038 + nsAutoString valueString; 1.1039 + // retrieve the specified/computed value of the property 1.1040 + nsresult res = GetCSSInlinePropertyBase(theElement, cssPropertyArray[index], 1.1041 + valueString, aStyleType); 1.1042 + NS_ENSURE_SUCCESS(res, res); 1.1043 + // append the value to aValueString (possibly with a leading whitespace) 1.1044 + if (index) { 1.1045 + aValueString.Append(char16_t(' ')); 1.1046 + } 1.1047 + aValueString.Append(valueString); 1.1048 + } 1.1049 + return NS_OK; 1.1050 +} 1.1051 + 1.1052 +// Does the node aNode (or its parent, if it's not an element node) have a CSS 1.1053 +// style equivalent to the HTML style aHTMLProperty/aHTMLAttribute/valueString? 1.1054 +// The value of aStyleType controls the styles we retrieve: specified or 1.1055 +// computed. The return value aIsSet is true if the CSS styles are set. 1.1056 +// 1.1057 +// The nsIContent variant returns aIsSet instead of using an out parameter, and 1.1058 +// does not modify aValue. 1.1059 +bool 1.1060 +nsHTMLCSSUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsIContent* aContent, 1.1061 + nsIAtom* aProperty, 1.1062 + const nsAString* aAttribute, 1.1063 + const nsAString& aValue, 1.1064 + StyleType aStyleType) 1.1065 +{ 1.1066 + MOZ_ASSERT(aContent && aProperty); 1.1067 + bool isSet; 1.1068 + nsAutoString value(aValue); 1.1069 + nsresult res = IsCSSEquivalentToHTMLInlineStyleSet(aContent->AsDOMNode(), 1.1070 + aProperty, aAttribute, 1.1071 + isSet, value, aStyleType); 1.1072 + NS_ASSERTION(NS_SUCCEEDED(res), "IsCSSEquivalentToHTMLInlineStyleSet failed"); 1.1073 + NS_ENSURE_SUCCESS(res, false); 1.1074 + return isSet; 1.1075 +} 1.1076 + 1.1077 +nsresult 1.1078 +nsHTMLCSSUtils::IsCSSEquivalentToHTMLInlineStyleSet(nsIDOMNode *aNode, 1.1079 + nsIAtom *aHTMLProperty, 1.1080 + const nsAString *aHTMLAttribute, 1.1081 + bool& aIsSet, 1.1082 + nsAString& valueString, 1.1083 + StyleType aStyleType) 1.1084 +{ 1.1085 + NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); 1.1086 + 1.1087 + nsAutoString htmlValueString(valueString); 1.1088 + aIsSet = false; 1.1089 + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); 1.1090 + do { 1.1091 + valueString.Assign(htmlValueString); 1.1092 + // get the value of the CSS equivalent styles 1.1093 + nsresult res = GetCSSEquivalentToHTMLInlineStyleSet(node, aHTMLProperty, aHTMLAttribute, 1.1094 + valueString, aStyleType); 1.1095 + NS_ENSURE_SUCCESS(res, res); 1.1096 + 1.1097 + // early way out if we can 1.1098 + if (valueString.IsEmpty()) { 1.1099 + return NS_OK; 1.1100 + } 1.1101 + 1.1102 + if (nsEditProperty::b == aHTMLProperty) { 1.1103 + if (valueString.EqualsLiteral("bold")) { 1.1104 + aIsSet = true; 1.1105 + } else if (valueString.EqualsLiteral("normal")) { 1.1106 + aIsSet = false; 1.1107 + } else if (valueString.EqualsLiteral("bolder")) { 1.1108 + aIsSet = true; 1.1109 + valueString.AssignLiteral("bold"); 1.1110 + } else { 1.1111 + int32_t weight = 0; 1.1112 + nsresult errorCode; 1.1113 + nsAutoString value(valueString); 1.1114 + weight = value.ToInteger(&errorCode); 1.1115 + if (400 < weight) { 1.1116 + aIsSet = true; 1.1117 + valueString.AssignLiteral("bold"); 1.1118 + } else { 1.1119 + aIsSet = false; 1.1120 + valueString.AssignLiteral("normal"); 1.1121 + } 1.1122 + } 1.1123 + } else if (nsEditProperty::i == aHTMLProperty) { 1.1124 + if (valueString.EqualsLiteral("italic") || 1.1125 + valueString.EqualsLiteral("oblique")) { 1.1126 + aIsSet = true; 1.1127 + } 1.1128 + } else if (nsEditProperty::u == aHTMLProperty) { 1.1129 + nsAutoString val; 1.1130 + val.AssignLiteral("underline"); 1.1131 + aIsSet = bool(ChangeCSSInlineStyleTxn::ValueIncludes(valueString, val, false)); 1.1132 + } else if (nsEditProperty::strike == aHTMLProperty) { 1.1133 + nsAutoString val; 1.1134 + val.AssignLiteral("line-through"); 1.1135 + aIsSet = bool(ChangeCSSInlineStyleTxn::ValueIncludes(valueString, val, false)); 1.1136 + } else if (aHTMLAttribute && 1.1137 + ((nsEditProperty::font == aHTMLProperty && 1.1138 + aHTMLAttribute->EqualsLiteral("color")) || 1.1139 + aHTMLAttribute->EqualsLiteral("bgcolor"))) { 1.1140 + if (htmlValueString.IsEmpty()) { 1.1141 + aIsSet = true; 1.1142 + } else { 1.1143 + nscolor rgba; 1.1144 + nsAutoString subStr; 1.1145 + htmlValueString.Right(subStr, htmlValueString.Length() - 1); 1.1146 + if (NS_ColorNameToRGB(htmlValueString, &rgba) || 1.1147 + NS_HexToRGB(subStr, &rgba)) { 1.1148 + nsAutoString htmlColor, tmpStr; 1.1149 + 1.1150 + if (NS_GET_A(rgba) != 255) { 1.1151 + // This should only be hit by the "transparent" keyword, which 1.1152 + // currently serializes to "transparent" (not "rgba(0, 0, 0, 0)"). 1.1153 + MOZ_ASSERT(NS_GET_R(rgba) == 0 && NS_GET_G(rgba) == 0 && 1.1154 + NS_GET_B(rgba) == 0 && NS_GET_A(rgba) == 0); 1.1155 + htmlColor.AppendLiteral("transparent"); 1.1156 + } else { 1.1157 + htmlColor.AppendLiteral("rgb("); 1.1158 + 1.1159 + NS_NAMED_LITERAL_STRING(comma, ", "); 1.1160 + 1.1161 + tmpStr.AppendInt(NS_GET_R(rgba), 10); 1.1162 + htmlColor.Append(tmpStr + comma); 1.1163 + 1.1164 + tmpStr.Truncate(); 1.1165 + tmpStr.AppendInt(NS_GET_G(rgba), 10); 1.1166 + htmlColor.Append(tmpStr + comma); 1.1167 + 1.1168 + tmpStr.Truncate(); 1.1169 + tmpStr.AppendInt(NS_GET_B(rgba), 10); 1.1170 + htmlColor.Append(tmpStr); 1.1171 + 1.1172 + htmlColor.Append(char16_t(')')); 1.1173 + } 1.1174 + 1.1175 + aIsSet = htmlColor.Equals(valueString, 1.1176 + nsCaseInsensitiveStringComparator()); 1.1177 + } else { 1.1178 + aIsSet = htmlValueString.Equals(valueString, 1.1179 + nsCaseInsensitiveStringComparator()); 1.1180 + } 1.1181 + } 1.1182 + } else if (nsEditProperty::tt == aHTMLProperty) { 1.1183 + aIsSet = StringBeginsWith(valueString, NS_LITERAL_STRING("monospace")); 1.1184 + } else if (nsEditProperty::font == aHTMLProperty && aHTMLAttribute && 1.1185 + aHTMLAttribute->EqualsLiteral("face")) { 1.1186 + if (!htmlValueString.IsEmpty()) { 1.1187 + const char16_t commaSpace[] = { char16_t(','), char16_t(' '), 0 }; 1.1188 + const char16_t comma[] = { char16_t(','), 0 }; 1.1189 + htmlValueString.ReplaceSubstring(commaSpace, comma); 1.1190 + nsAutoString valueStringNorm(valueString); 1.1191 + valueStringNorm.ReplaceSubstring(commaSpace, comma); 1.1192 + aIsSet = htmlValueString.Equals(valueStringNorm, 1.1193 + nsCaseInsensitiveStringComparator()); 1.1194 + } else { 1.1195 + // ignore this, it's TT or our default 1.1196 + nsAutoString valueStringLower; 1.1197 + ToLowerCase(valueString, valueStringLower); 1.1198 + aIsSet = !valueStringLower.EqualsLiteral("monospace") && 1.1199 + !valueStringLower.EqualsLiteral("serif"); 1.1200 + } 1.1201 + return NS_OK; 1.1202 + } else if (aHTMLAttribute && aHTMLAttribute->EqualsLiteral("align")) { 1.1203 + aIsSet = true; 1.1204 + } else { 1.1205 + aIsSet = false; 1.1206 + return NS_OK; 1.1207 + } 1.1208 + 1.1209 + if (!htmlValueString.IsEmpty() && 1.1210 + htmlValueString.Equals(valueString, 1.1211 + nsCaseInsensitiveStringComparator())) { 1.1212 + aIsSet = true; 1.1213 + } 1.1214 + 1.1215 + if (htmlValueString.EqualsLiteral("-moz-editor-invert-value")) { 1.1216 + aIsSet = !aIsSet; 1.1217 + } 1.1218 + 1.1219 + if (nsEditProperty::u == aHTMLProperty || nsEditProperty::strike == aHTMLProperty) { 1.1220 + // unfortunately, the value of the text-decoration property is not inherited. 1.1221 + // that means that we have to look at ancestors of node to see if they are underlined 1.1222 + node = node->GetParentElement(); // set to null if it's not a dom element 1.1223 + } 1.1224 + } while ((nsEditProperty::u == aHTMLProperty || nsEditProperty::strike == aHTMLProperty) && 1.1225 + !aIsSet && node); 1.1226 + return NS_OK; 1.1227 +} 1.1228 + 1.1229 +void 1.1230 +nsHTMLCSSUtils::SetCSSEnabled(bool aIsCSSPrefChecked) 1.1231 +{ 1.1232 + mIsCSSPrefChecked = aIsCSSPrefChecked; 1.1233 +} 1.1234 + 1.1235 +bool 1.1236 +nsHTMLCSSUtils::IsCSSPrefChecked() 1.1237 +{ 1.1238 + return mIsCSSPrefChecked ; 1.1239 +} 1.1240 + 1.1241 +// ElementsSameStyle compares two elements and checks if they have the same 1.1242 +// specified CSS declarations in the STYLE attribute 1.1243 +// The answer is always negative if at least one of them carries an ID or a class 1.1244 +bool 1.1245 +nsHTMLCSSUtils::ElementsSameStyle(nsIDOMNode *aFirstNode, nsIDOMNode *aSecondNode) 1.1246 +{ 1.1247 + nsCOMPtr<dom::Element> firstElement = do_QueryInterface(aFirstNode); 1.1248 + nsCOMPtr<dom::Element> secondElement = do_QueryInterface(aSecondNode); 1.1249 + 1.1250 + NS_ASSERTION((firstElement && secondElement), "Non element nodes passed to ElementsSameStyle."); 1.1251 + NS_ENSURE_TRUE(firstElement, false); 1.1252 + NS_ENSURE_TRUE(secondElement, false); 1.1253 + 1.1254 + return ElementsSameStyle(firstElement, secondElement); 1.1255 +} 1.1256 + 1.1257 +bool 1.1258 +nsHTMLCSSUtils::ElementsSameStyle(dom::Element* aFirstElement, 1.1259 + dom::Element* aSecondElement) 1.1260 +{ 1.1261 + MOZ_ASSERT(aFirstElement); 1.1262 + MOZ_ASSERT(aSecondElement); 1.1263 + 1.1264 + if (aFirstElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id) || 1.1265 + aSecondElement->HasAttr(kNameSpaceID_None, nsGkAtoms::id)) { 1.1266 + // at least one of the spans carries an ID ; suspect a CSS rule applies to it and 1.1267 + // refuse to merge the nodes 1.1268 + return false; 1.1269 + } 1.1270 + 1.1271 + nsAutoString firstClass, secondClass; 1.1272 + bool isFirstClassSet = aFirstElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, firstClass); 1.1273 + bool isSecondClassSet = aSecondElement->GetAttr(kNameSpaceID_None, nsGkAtoms::_class, secondClass); 1.1274 + if (isFirstClassSet && isSecondClassSet) { 1.1275 + // both spans carry a class, let's compare them 1.1276 + if (!firstClass.Equals(secondClass)) { 1.1277 + // WARNING : technically, the comparison just above is questionable : 1.1278 + // from a pure HTML/CSS point of view class="a b" is NOT the same than 1.1279 + // class="b a" because a CSS rule could test the exact value of the class 1.1280 + // attribute to be "a b" for instance ; from a user's point of view, a 1.1281 + // wysiwyg editor should probably NOT make any difference. CSS people 1.1282 + // need to discuss this issue before any modification. 1.1283 + return false; 1.1284 + } 1.1285 + } else if (isFirstClassSet || isSecondClassSet) { 1.1286 + // one span only carries a class, early way out 1.1287 + return false; 1.1288 + } 1.1289 + 1.1290 + nsCOMPtr<nsIDOMCSSStyleDeclaration> firstCSSDecl, secondCSSDecl; 1.1291 + uint32_t firstLength, secondLength; 1.1292 + nsresult rv = GetInlineStyles(aFirstElement, getter_AddRefs(firstCSSDecl), &firstLength); 1.1293 + if (NS_FAILED(rv) || !firstCSSDecl) { 1.1294 + return false; 1.1295 + } 1.1296 + rv = GetInlineStyles(aSecondElement, getter_AddRefs(secondCSSDecl), &secondLength); 1.1297 + if (NS_FAILED(rv) || !secondCSSDecl) { 1.1298 + return false; 1.1299 + } 1.1300 + 1.1301 + if (firstLength != secondLength) { 1.1302 + // early way out if we can 1.1303 + return false; 1.1304 + } 1.1305 + 1.1306 + if (!firstLength) { 1.1307 + // no inline style ! 1.1308 + return true; 1.1309 + } 1.1310 + 1.1311 + nsAutoString propertyNameString; 1.1312 + nsAutoString firstValue, secondValue; 1.1313 + for (uint32_t i = 0; i < firstLength; i++) { 1.1314 + firstCSSDecl->Item(i, propertyNameString); 1.1315 + firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); 1.1316 + secondCSSDecl->GetPropertyValue(propertyNameString, secondValue); 1.1317 + if (!firstValue.Equals(secondValue)) { 1.1318 + return false; 1.1319 + } 1.1320 + } 1.1321 + for (uint32_t i = 0; i < secondLength; i++) { 1.1322 + secondCSSDecl->Item(i, propertyNameString); 1.1323 + secondCSSDecl->GetPropertyValue(propertyNameString, secondValue); 1.1324 + firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); 1.1325 + if (!firstValue.Equals(secondValue)) { 1.1326 + return false; 1.1327 + } 1.1328 + } 1.1329 + 1.1330 + return true; 1.1331 +} 1.1332 + 1.1333 +nsresult 1.1334 +nsHTMLCSSUtils::GetInlineStyles(dom::Element* aElement, 1.1335 + nsIDOMCSSStyleDeclaration** aCssDecl, 1.1336 + uint32_t* aLength) 1.1337 +{ 1.1338 + return GetInlineStyles(static_cast<nsISupports*>(aElement), aCssDecl, aLength); 1.1339 +} 1.1340 + 1.1341 +nsresult 1.1342 +nsHTMLCSSUtils::GetInlineStyles(nsIDOMElement* aElement, 1.1343 + nsIDOMCSSStyleDeclaration** aCssDecl, 1.1344 + uint32_t* aLength) 1.1345 +{ 1.1346 + return GetInlineStyles(static_cast<nsISupports*>(aElement), aCssDecl, aLength); 1.1347 +} 1.1348 + 1.1349 +nsresult 1.1350 +nsHTMLCSSUtils::GetInlineStyles(nsISupports *aElement, 1.1351 + nsIDOMCSSStyleDeclaration **aCssDecl, 1.1352 + uint32_t *aLength) 1.1353 +{ 1.1354 + NS_ENSURE_TRUE(aElement && aLength, NS_ERROR_NULL_POINTER); 1.1355 + *aLength = 0; 1.1356 + nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyles = do_QueryInterface(aElement); 1.1357 + NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); 1.1358 + 1.1359 + nsresult res = inlineStyles->GetStyle(aCssDecl); 1.1360 + NS_ENSURE_SUCCESS(res, NS_ERROR_NULL_POINTER); 1.1361 + MOZ_ASSERT(*aCssDecl); 1.1362 + 1.1363 + (*aCssDecl)->GetLength(aLength); 1.1364 + return NS_OK; 1.1365 +} 1.1366 + 1.1367 +already_AddRefed<nsIDOMElement> 1.1368 +nsHTMLCSSUtils::GetElementContainerOrSelf(nsIDOMNode* aNode) 1.1369 +{ 1.1370 + nsCOMPtr<nsINode> node = do_QueryInterface(aNode); 1.1371 + NS_ENSURE_TRUE(node, nullptr); 1.1372 + nsCOMPtr<nsIDOMElement> element = 1.1373 + do_QueryInterface(GetElementContainerOrSelf(node)); 1.1374 + return element.forget(); 1.1375 +} 1.1376 + 1.1377 +dom::Element* 1.1378 +nsHTMLCSSUtils::GetElementContainerOrSelf(nsINode* aNode) 1.1379 +{ 1.1380 + MOZ_ASSERT(aNode); 1.1381 + if (nsIDOMNode::DOCUMENT_NODE == aNode->NodeType()) { 1.1382 + return nullptr; 1.1383 + } 1.1384 + 1.1385 + nsINode* node = aNode; 1.1386 + // Loop until we find an element. 1.1387 + while (node && !node->IsElement()) { 1.1388 + node = node->GetParentNode(); 1.1389 + } 1.1390 + 1.1391 + NS_ENSURE_TRUE(node, nullptr); 1.1392 + return node->AsElement(); 1.1393 +} 1.1394 + 1.1395 +nsresult 1.1396 +nsHTMLCSSUtils::SetCSSProperty(nsIDOMElement * aElement, 1.1397 + const nsAString & aProperty, 1.1398 + const nsAString & aValue) 1.1399 +{ 1.1400 + nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl; 1.1401 + uint32_t length; 1.1402 + nsresult res = GetInlineStyles(aElement, getter_AddRefs(cssDecl), &length); 1.1403 + if (NS_FAILED(res) || !cssDecl) return res; 1.1404 + 1.1405 + return cssDecl->SetProperty(aProperty, 1.1406 + aValue, 1.1407 + EmptyString()); 1.1408 +} 1.1409 + 1.1410 +nsresult 1.1411 +nsHTMLCSSUtils::SetCSSPropertyPixels(nsIDOMElement * aElement, 1.1412 + const nsAString & aProperty, 1.1413 + int32_t aIntValue) 1.1414 +{ 1.1415 + nsAutoString s; 1.1416 + s.AppendInt(aIntValue); 1.1417 + return SetCSSProperty(aElement, aProperty, s + NS_LITERAL_STRING("px")); 1.1418 +}