CSSEditUtils.cpp (48132B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "CSSEditUtils.h" 7 8 #include "ChangeStyleTransaction.h" 9 #include "EditorDOMAPIWrapper.h" 10 #include "HTMLEditHelpers.h" 11 #include "HTMLEditor.h" 12 #include "HTMLEditUtils.h" 13 14 #include "mozilla/Assertions.h" 15 #include "mozilla/DeclarationBlock.h" 16 #include "mozilla/mozalloc.h" 17 #include "mozilla/Preferences.h" 18 #include "mozilla/ServoCSSParser.h" 19 #include "mozilla/StaticPrefs_browser.h" 20 #include "mozilla/StaticPrefs_editor.h" 21 #include "mozilla/dom/Document.h" 22 #include "mozilla/dom/Element.h" 23 #include "nsAString.h" 24 #include "nsCOMPtr.h" 25 #include "nsCSSProps.h" 26 #include "nsColor.h" 27 #include "nsComputedDOMStyle.h" 28 #include "nsDebug.h" 29 #include "nsDependentSubstring.h" 30 #include "nsError.h" 31 #include "nsGkAtoms.h" 32 #include "nsAtom.h" 33 #include "nsIContent.h" 34 #include "nsICSSDeclaration.h" 35 #include "nsINode.h" 36 #include "nsISupportsImpl.h" 37 #include "nsISupportsUtils.h" 38 #include "nsLiteralString.h" 39 #include "nsPIDOMWindow.h" 40 #include "nsReadableUtils.h" 41 #include "nsString.h" 42 #include "nsStringFwd.h" 43 #include "nsStringIterator.h" 44 #include "nsStyledElement.h" 45 #include "nsUnicharUtils.h" 46 47 namespace mozilla { 48 49 using namespace dom; 50 51 static void ProcessBValue(const nsAString* aInputString, 52 nsAString& aOutputString, 53 const char* aDefaultValueString, 54 const char* aPrependString, 55 const char* aAppendString) { 56 if (aInputString && aInputString->EqualsLiteral("-moz-editor-invert-value")) { 57 aOutputString.AssignLiteral("normal"); 58 } else { 59 aOutputString.AssignLiteral("bold"); 60 } 61 } 62 63 static void ProcessDefaultValue(const nsAString* aInputString, 64 nsAString& aOutputString, 65 const char* aDefaultValueString, 66 const char* aPrependString, 67 const char* aAppendString) { 68 CopyASCIItoUTF16(MakeStringSpan(aDefaultValueString), aOutputString); 69 } 70 71 static void ProcessSameValue(const nsAString* aInputString, 72 nsAString& aOutputString, 73 const char* aDefaultValueString, 74 const char* aPrependString, 75 const char* aAppendString) { 76 if (aInputString) { 77 aOutputString.Assign(*aInputString); 78 } else 79 aOutputString.Truncate(); 80 } 81 82 static void ProcessExtendedValue(const nsAString* aInputString, 83 nsAString& aOutputString, 84 const char* aDefaultValueString, 85 const char* aPrependString, 86 const char* aAppendString) { 87 aOutputString.Truncate(); 88 if (aInputString) { 89 if (aPrependString) { 90 AppendASCIItoUTF16(MakeStringSpan(aPrependString), aOutputString); 91 } 92 aOutputString.Append(*aInputString); 93 if (aAppendString) { 94 AppendASCIItoUTF16(MakeStringSpan(aAppendString), aOutputString); 95 } 96 } 97 } 98 99 static void ProcessLengthValue(const nsAString* aInputString, 100 nsAString& aOutputString, 101 const char* aDefaultValueString, 102 const char* aPrependString, 103 const char* aAppendString) { 104 aOutputString.Truncate(); 105 if (aInputString) { 106 aOutputString.Append(*aInputString); 107 if (-1 == aOutputString.FindChar(char16_t('%'))) { 108 aOutputString.AppendLiteral("px"); 109 } 110 } 111 } 112 113 static void ProcessListStyleTypeValue(const nsAString* aInputString, 114 nsAString& aOutputString, 115 const char* aDefaultValueString, 116 const char* aPrependString, 117 const char* aAppendString) { 118 aOutputString.Truncate(); 119 if (aInputString) { 120 if (aInputString->EqualsLiteral("1")) { 121 aOutputString.AppendLiteral("decimal"); 122 } else if (aInputString->EqualsLiteral("a")) { 123 aOutputString.AppendLiteral("lower-alpha"); 124 } else if (aInputString->EqualsLiteral("A")) { 125 aOutputString.AppendLiteral("upper-alpha"); 126 } else if (aInputString->EqualsLiteral("i")) { 127 aOutputString.AppendLiteral("lower-roman"); 128 } else if (aInputString->EqualsLiteral("I")) { 129 aOutputString.AppendLiteral("upper-roman"); 130 } else if (aInputString->EqualsLiteral("square") || 131 aInputString->EqualsLiteral("circle") || 132 aInputString->EqualsLiteral("disc")) { 133 aOutputString.Append(*aInputString); 134 } 135 } 136 } 137 138 static void ProcessMarginLeftValue(const nsAString* aInputString, 139 nsAString& aOutputString, 140 const char* aDefaultValueString, 141 const char* aPrependString, 142 const char* aAppendString) { 143 aOutputString.Truncate(); 144 if (aInputString) { 145 if (aInputString->EqualsLiteral("center") || 146 aInputString->EqualsLiteral("-moz-center")) { 147 aOutputString.AppendLiteral("auto"); 148 } else if (aInputString->EqualsLiteral("right") || 149 aInputString->EqualsLiteral("-moz-right")) { 150 aOutputString.AppendLiteral("auto"); 151 } else { 152 aOutputString.AppendLiteral("0px"); 153 } 154 } 155 } 156 157 static void ProcessMarginRightValue(const nsAString* aInputString, 158 nsAString& aOutputString, 159 const char* aDefaultValueString, 160 const char* aPrependString, 161 const char* aAppendString) { 162 aOutputString.Truncate(); 163 if (aInputString) { 164 if (aInputString->EqualsLiteral("center") || 165 aInputString->EqualsLiteral("-moz-center")) { 166 aOutputString.AppendLiteral("auto"); 167 } else if (aInputString->EqualsLiteral("left") || 168 aInputString->EqualsLiteral("-moz-left")) { 169 aOutputString.AppendLiteral("auto"); 170 } else { 171 aOutputString.AppendLiteral("0px"); 172 } 173 } 174 } 175 176 #define CSS_EQUIV_TABLE_NONE {CSSEditUtils::eCSSEditableProperty_NONE, 0} 177 178 const CSSEditUtils::CSSEquivTable boldEquivTable[] = { 179 {CSSEditUtils::eCSSEditableProperty_font_weight, true, false, ProcessBValue, 180 nullptr, nullptr, nullptr}, 181 CSS_EQUIV_TABLE_NONE}; 182 183 const CSSEditUtils::CSSEquivTable italicEquivTable[] = { 184 {CSSEditUtils::eCSSEditableProperty_font_style, true, false, 185 ProcessDefaultValue, "italic", nullptr, nullptr}, 186 CSS_EQUIV_TABLE_NONE}; 187 188 const CSSEditUtils::CSSEquivTable underlineEquivTable[] = { 189 {CSSEditUtils::eCSSEditableProperty_text_decoration, true, false, 190 ProcessDefaultValue, "underline", nullptr, nullptr}, 191 CSS_EQUIV_TABLE_NONE}; 192 193 const CSSEditUtils::CSSEquivTable strikeEquivTable[] = { 194 {CSSEditUtils::eCSSEditableProperty_text_decoration, true, false, 195 ProcessDefaultValue, "line-through", nullptr, nullptr}, 196 CSS_EQUIV_TABLE_NONE}; 197 198 const CSSEditUtils::CSSEquivTable ttEquivTable[] = { 199 {CSSEditUtils::eCSSEditableProperty_font_family, true, false, 200 ProcessDefaultValue, "monospace", nullptr, nullptr}, 201 CSS_EQUIV_TABLE_NONE}; 202 203 const CSSEditUtils::CSSEquivTable fontColorEquivTable[] = { 204 {CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue, 205 nullptr, nullptr, nullptr}, 206 CSS_EQUIV_TABLE_NONE}; 207 208 const CSSEditUtils::CSSEquivTable fontFaceEquivTable[] = { 209 {CSSEditUtils::eCSSEditableProperty_font_family, true, false, 210 ProcessSameValue, nullptr, nullptr, nullptr}, 211 CSS_EQUIV_TABLE_NONE}; 212 213 const CSSEditUtils::CSSEquivTable fontSizeEquivTable[] = { 214 {CSSEditUtils::eCSSEditableProperty_font_size, true, false, 215 ProcessSameValue, nullptr, nullptr, nullptr}, 216 CSS_EQUIV_TABLE_NONE}; 217 218 const CSSEditUtils::CSSEquivTable bgcolorEquivTable[] = { 219 {CSSEditUtils::eCSSEditableProperty_background_color, true, false, 220 ProcessSameValue, nullptr, nullptr, nullptr}, 221 CSS_EQUIV_TABLE_NONE}; 222 223 const CSSEditUtils::CSSEquivTable backgroundImageEquivTable[] = { 224 {CSSEditUtils::eCSSEditableProperty_background_image, true, true, 225 ProcessExtendedValue, nullptr, "url(", ")"}, 226 CSS_EQUIV_TABLE_NONE}; 227 228 const CSSEditUtils::CSSEquivTable textColorEquivTable[] = { 229 {CSSEditUtils::eCSSEditableProperty_color, true, false, ProcessSameValue, 230 nullptr, nullptr, nullptr}, 231 CSS_EQUIV_TABLE_NONE}; 232 233 const CSSEditUtils::CSSEquivTable borderEquivTable[] = { 234 {CSSEditUtils::eCSSEditableProperty_border, true, false, 235 ProcessExtendedValue, nullptr, nullptr, "px solid"}, 236 CSS_EQUIV_TABLE_NONE}; 237 238 const CSSEditUtils::CSSEquivTable textAlignEquivTable[] = { 239 {CSSEditUtils::eCSSEditableProperty_text_align, true, false, 240 ProcessSameValue, nullptr, nullptr, nullptr}, 241 CSS_EQUIV_TABLE_NONE}; 242 243 const CSSEditUtils::CSSEquivTable captionAlignEquivTable[] = { 244 {CSSEditUtils::eCSSEditableProperty_caption_side, true, false, 245 ProcessSameValue, nullptr, nullptr, nullptr}, 246 CSS_EQUIV_TABLE_NONE}; 247 248 const CSSEditUtils::CSSEquivTable verticalAlignEquivTable[] = { 249 {CSSEditUtils::eCSSEditableProperty_vertical_align, true, false, 250 ProcessSameValue, nullptr, nullptr, nullptr}, 251 CSS_EQUIV_TABLE_NONE}; 252 253 const CSSEditUtils::CSSEquivTable nowrapEquivTable[] = { 254 {CSSEditUtils::eCSSEditableProperty_whitespace, true, false, 255 ProcessDefaultValue, "nowrap", nullptr, nullptr}, 256 CSS_EQUIV_TABLE_NONE}; 257 258 const CSSEditUtils::CSSEquivTable widthEquivTable[] = { 259 {CSSEditUtils::eCSSEditableProperty_width, true, false, ProcessLengthValue, 260 nullptr, nullptr, nullptr}, 261 CSS_EQUIV_TABLE_NONE}; 262 263 const CSSEditUtils::CSSEquivTable heightEquivTable[] = { 264 {CSSEditUtils::eCSSEditableProperty_height, true, false, ProcessLengthValue, 265 nullptr, nullptr, nullptr}, 266 CSS_EQUIV_TABLE_NONE}; 267 268 const CSSEditUtils::CSSEquivTable listStyleTypeEquivTable[] = { 269 {CSSEditUtils::eCSSEditableProperty_list_style_type, true, true, 270 ProcessListStyleTypeValue, nullptr, nullptr, nullptr}, 271 CSS_EQUIV_TABLE_NONE}; 272 273 const CSSEditUtils::CSSEquivTable tableAlignEquivTable[] = { 274 {CSSEditUtils::eCSSEditableProperty_text_align, false, false, 275 ProcessDefaultValue, "left", nullptr, nullptr}, 276 {CSSEditUtils::eCSSEditableProperty_margin_left, true, false, 277 ProcessMarginLeftValue, nullptr, nullptr, nullptr}, 278 {CSSEditUtils::eCSSEditableProperty_margin_right, true, false, 279 ProcessMarginRightValue, nullptr, nullptr, nullptr}, 280 CSS_EQUIV_TABLE_NONE}; 281 282 const CSSEditUtils::CSSEquivTable hrAlignEquivTable[] = { 283 {CSSEditUtils::eCSSEditableProperty_margin_left, true, false, 284 ProcessMarginLeftValue, nullptr, nullptr, nullptr}, 285 {CSSEditUtils::eCSSEditableProperty_margin_right, true, false, 286 ProcessMarginRightValue, nullptr, nullptr, nullptr}, 287 CSS_EQUIV_TABLE_NONE}; 288 289 #undef CSS_EQUIV_TABLE_NONE 290 291 // static 292 bool CSSEditUtils::IsCSSEditableStyle(const Element& aElement, 293 const EditorElementStyle& aStyle) { 294 return CSSEditUtils::IsCSSEditableStyle(*aElement.NodeInfo()->NameAtom(), 295 aStyle); 296 } 297 298 // static 299 bool CSSEditUtils::IsCSSEditableStyle(const nsAtom& aTagName, 300 const EditorElementStyle& aStyle) { 301 nsStaticAtom* const htmlProperty = 302 aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mHTMLProperty : nullptr; 303 nsAtom* const attributeOrStyle = aStyle.IsInlineStyle() 304 ? aStyle.AsInlineStyle().mAttribute.get() 305 : aStyle.Style(); 306 307 // HTML inline styles <b>, <i>, <tt> (chrome only), <u>, <strike>, <font 308 // color> and <font style>. 309 if (nsGkAtoms::b == htmlProperty || nsGkAtoms::i == htmlProperty || 310 nsGkAtoms::tt == htmlProperty || nsGkAtoms::u == htmlProperty || 311 nsGkAtoms::strike == htmlProperty || 312 (nsGkAtoms::font == htmlProperty && 313 (attributeOrStyle == nsGkAtoms::color || 314 attributeOrStyle == nsGkAtoms::face))) { 315 return true; 316 } 317 318 // ALIGN attribute on elements supporting it 319 if (attributeOrStyle == nsGkAtoms::align && 320 (&aTagName == nsGkAtoms::div || &aTagName == nsGkAtoms::p || 321 &aTagName == nsGkAtoms::h1 || &aTagName == nsGkAtoms::h2 || 322 &aTagName == nsGkAtoms::h3 || &aTagName == nsGkAtoms::h4 || 323 &aTagName == nsGkAtoms::h5 || &aTagName == nsGkAtoms::h6 || 324 &aTagName == nsGkAtoms::td || &aTagName == nsGkAtoms::th || 325 &aTagName == nsGkAtoms::table || &aTagName == nsGkAtoms::hr || 326 // For the above, why not use 327 // HTMLEditUtils::SupportsAlignAttr? 328 // It also checks for tbody, tfoot, thead. 329 // Let's add the following elements here even 330 // if "align" has a different meaning for them 331 &aTagName == nsGkAtoms::legend || &aTagName == nsGkAtoms::caption)) { 332 return true; 333 } 334 335 if (attributeOrStyle == nsGkAtoms::valign && 336 (&aTagName == nsGkAtoms::col || &aTagName == nsGkAtoms::colgroup || 337 &aTagName == nsGkAtoms::tbody || &aTagName == nsGkAtoms::td || 338 &aTagName == nsGkAtoms::th || &aTagName == nsGkAtoms::tfoot || 339 &aTagName == nsGkAtoms::thead || &aTagName == nsGkAtoms::tr)) { 340 return true; 341 } 342 343 // attributes TEXT, BACKGROUND and BGCOLOR on <body> 344 if (&aTagName == nsGkAtoms::body && 345 (attributeOrStyle == nsGkAtoms::text || 346 attributeOrStyle == nsGkAtoms::background || 347 attributeOrStyle == nsGkAtoms::bgcolor)) { 348 return true; 349 } 350 351 // attribute BGCOLOR on other elements 352 if (attributeOrStyle == nsGkAtoms::bgcolor) { 353 return true; 354 } 355 356 // attributes HEIGHT, WIDTH and NOWRAP on <td> and <th> 357 if ((&aTagName == nsGkAtoms::td || &aTagName == nsGkAtoms::th) && 358 (attributeOrStyle == nsGkAtoms::height || 359 attributeOrStyle == nsGkAtoms::width || 360 attributeOrStyle == nsGkAtoms::nowrap)) { 361 return true; 362 } 363 364 // attributes HEIGHT and WIDTH on <table> 365 if (&aTagName == nsGkAtoms::table && (attributeOrStyle == nsGkAtoms::height || 366 attributeOrStyle == nsGkAtoms::width)) { 367 return true; 368 } 369 370 // attributes SIZE and WIDTH on <hr> 371 if (&aTagName == nsGkAtoms::hr && (attributeOrStyle == nsGkAtoms::size || 372 attributeOrStyle == nsGkAtoms::width)) { 373 return true; 374 } 375 376 // attribute TYPE on <ol>, <ul> and <li> 377 if (attributeOrStyle == nsGkAtoms::type && 378 (&aTagName == nsGkAtoms::ol || &aTagName == nsGkAtoms::ul || 379 &aTagName == nsGkAtoms::li)) { 380 return true; 381 } 382 383 if (&aTagName == nsGkAtoms::img && (attributeOrStyle == nsGkAtoms::border || 384 attributeOrStyle == nsGkAtoms::width || 385 attributeOrStyle == nsGkAtoms::height)) { 386 return true; 387 } 388 389 // other elements that we can align using CSS even if they 390 // can't carry the html ALIGN attribute 391 if (attributeOrStyle == nsGkAtoms::align && 392 (&aTagName == nsGkAtoms::ul || &aTagName == nsGkAtoms::ol || 393 &aTagName == nsGkAtoms::dl || &aTagName == nsGkAtoms::li || 394 &aTagName == nsGkAtoms::dd || &aTagName == nsGkAtoms::dt || 395 &aTagName == nsGkAtoms::address || &aTagName == nsGkAtoms::pre)) { 396 return true; 397 } 398 399 return false; 400 } 401 402 // The lowest level above the transaction; adds the CSS declaration 403 // "aProperty : aValue" to the inline styles carried by aStyledElement 404 405 // static 406 nsresult CSSEditUtils::SetCSSPropertyInternal(HTMLEditor& aHTMLEditor, 407 nsStyledElement& aStyledElement, 408 nsAtom& aProperty, 409 const nsAString& aValue, 410 bool aSuppressTxn) { 411 const RefPtr<ChangeStyleTransaction> transaction = 412 ChangeStyleTransaction::Create(aHTMLEditor, aStyledElement, aProperty, 413 aValue); 414 if (aSuppressTxn) { 415 nsresult rv = transaction->DoTransaction(); 416 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 417 "ChangeStyleTransaction::DoTransaction() failed"); 418 return rv; 419 } 420 nsresult rv = aHTMLEditor.DoTransactionInternal(transaction); 421 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 422 return NS_ERROR_EDITOR_DESTROYED; 423 } 424 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 425 "EditorBase::DoTransactionInternal() failed"); 426 return rv; 427 } 428 429 // static 430 nsresult CSSEditUtils::SetCSSPropertyPixelsWithTransaction( 431 HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty, 432 int32_t aIntValue) { 433 nsAutoString s; 434 s.AppendInt(aIntValue); 435 nsresult rv = SetCSSPropertyWithTransaction(aHTMLEditor, aStyledElement, 436 aProperty, s + u"px"_ns); 437 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 438 "CSSEditUtils::SetCSSPropertyWithTransaction() failed"); 439 return rv; 440 } 441 442 // static 443 nsresult CSSEditUtils::SetCSSPropertyPixelsWithoutTransaction( 444 HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, 445 const nsAtom& aProperty, int32_t aIntValue) { 446 nsAutoCString propertyNameString; 447 aProperty.ToUTF8String(propertyNameString); 448 449 nsAutoCString s; 450 s.AppendInt(aIntValue); 451 s.AppendLiteral("px"); 452 453 nsresult rv = AutoCSSDeclarationAPIWrapper(aHTMLEditor, aStyledElement) 454 .SetProperty(propertyNameString, s, EmptyCString()); 455 if (NS_FAILED(rv)) { 456 NS_WARNING("AutoCSSDeclarationAPIWrapper::SetProperty() failed"); 457 return rv; 458 } 459 460 return NS_OK; 461 } 462 463 // The lowest level above the transaction; removes the value aValue from the 464 // list of values specified for the CSS property aProperty, or totally remove 465 // the declaration if this property accepts only one value 466 467 // static 468 nsresult CSSEditUtils::RemoveCSSPropertyInternal( 469 HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty, 470 const nsAString& aValue, bool aSuppressTxn) { 471 const RefPtr<ChangeStyleTransaction> transaction = 472 ChangeStyleTransaction::CreateToRemove(aHTMLEditor, aStyledElement, 473 aProperty, aValue); 474 if (aSuppressTxn) { 475 nsresult rv = transaction->DoTransaction(); 476 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 477 "ChangeStyleTransaction::DoTransaction() failed"); 478 return rv; 479 } 480 nsresult rv = aHTMLEditor.DoTransactionInternal(transaction); 481 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 482 return NS_ERROR_EDITOR_DESTROYED; 483 } 484 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 485 "EditorBase::DoTransactionInternal() failed"); 486 return rv; 487 } 488 489 // static 490 nsresult CSSEditUtils::GetSpecifiedProperty(nsIContent& aContent, 491 nsAtom& aCSSProperty, 492 nsAString& aValue) { 493 nsresult rv = 494 GetSpecifiedCSSInlinePropertyBase(aContent, aCSSProperty, aValue); 495 NS_WARNING_ASSERTION( 496 NS_SUCCEEDED(rv), 497 "CSSEditUtils::GeSpecifiedCSSInlinePropertyBase() failed"); 498 return rv; 499 } 500 501 // static 502 nsresult CSSEditUtils::GetComputedProperty(nsIContent& aContent, 503 nsAtom& aCSSProperty, 504 nsAString& aValue) { 505 nsresult rv = 506 GetComputedCSSInlinePropertyBase(aContent, aCSSProperty, aValue); 507 NS_WARNING_ASSERTION( 508 NS_SUCCEEDED(rv), 509 "CSSEditUtils::GetComputedCSSInlinePropertyBase() failed"); 510 return rv; 511 } 512 513 // static 514 nsresult CSSEditUtils::GetComputedCSSInlinePropertyBase(nsIContent& aContent, 515 nsAtom& aCSSProperty, 516 nsAString& aValue) { 517 aValue.Truncate(); 518 519 RefPtr<Element> element = aContent.GetAsElementOrParentElement(); 520 if (NS_WARN_IF(!element)) { 521 return NS_ERROR_INVALID_ARG; 522 } 523 524 // Get the all the computed css styles attached to the element node 525 RefPtr<nsComputedDOMStyle> computedDOMStyle = GetComputedStyle(element); 526 if (NS_WARN_IF(!computedDOMStyle)) { 527 return NS_ERROR_INVALID_ARG; 528 } 529 530 // from these declarations, get the one we want and that one only 531 // 532 // FIXME(bug 1606994): nsAtomCString copies, we should just keep around the 533 // property id. 534 // 535 // FIXME: Maybe we can avoid copying aValue too, though it's no worse than 536 // what we used to do. 537 nsAutoCString value; 538 computedDOMStyle->GetPropertyValue(nsAtomCString(&aCSSProperty), value); 539 CopyUTF8toUTF16(value, aValue); 540 return NS_OK; 541 } 542 543 // static 544 nsresult CSSEditUtils::GetSpecifiedCSSInlinePropertyBase(nsIContent& aContent, 545 nsAtom& aCSSProperty, 546 nsAString& aValue) { 547 aValue.Truncate(); 548 549 RefPtr<Element> element = aContent.GetAsElementOrParentElement(); 550 if (NS_WARN_IF(!element)) { 551 return NS_ERROR_INVALID_ARG; 552 } 553 554 RefPtr<DeclarationBlock> decl = element->GetInlineStyleDeclaration(); 555 if (!decl) { 556 return NS_OK; 557 } 558 559 // FIXME: Same comments as above. 560 NonCustomCSSPropertyId prop = 561 nsCSSProps::LookupProperty(nsAtomCString(&aCSSProperty)); 562 MOZ_ASSERT(prop != eCSSProperty_UNKNOWN); 563 564 nsAutoCString value; 565 decl->GetPropertyValueById(prop, value); 566 CopyUTF8toUTF16(value, aValue); 567 return NS_OK; 568 } 569 570 // static 571 already_AddRefed<nsComputedDOMStyle> CSSEditUtils::GetComputedStyle( 572 Element* aElement) { 573 MOZ_ASSERT(aElement); 574 575 Document* document = aElement->GetComposedDoc(); 576 if (NS_WARN_IF(!document)) { 577 return nullptr; 578 } 579 580 RefPtr<nsComputedDOMStyle> computedDOMStyle = NS_NewComputedDOMStyle( 581 aElement, u""_ns, document, nsComputedDOMStyle::StyleType::All, 582 IgnoreErrors()); 583 return computedDOMStyle.forget(); 584 } 585 586 // remove the CSS style "aProperty : aPropertyValue" and possibly remove the 587 // whole node if it is a span and if its only attribute is _moz_dirty 588 589 // static 590 Result<EditorDOMPoint, nsresult> 591 CSSEditUtils::RemoveCSSInlineStyleWithTransaction( 592 HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom* aProperty, 593 const nsAString& aPropertyValue) { 594 // remove the property from the style attribute 595 nsresult rv = RemoveCSSPropertyWithTransaction(aHTMLEditor, aStyledElement, 596 *aProperty, aPropertyValue); 597 if (NS_FAILED(rv)) { 598 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed"); 599 return Err(rv); 600 } 601 602 if (!aStyledElement.IsHTMLElement(nsGkAtoms::span) || 603 HTMLEditUtils::ElementHasAttribute(aStyledElement)) { 604 return EditorDOMPoint(); 605 } 606 607 Result<EditorDOMPoint, nsresult> unwrapStyledElementResult = 608 aHTMLEditor.RemoveContainerWithTransaction(aStyledElement); 609 NS_WARNING_ASSERTION(unwrapStyledElementResult.isOk(), 610 "HTMLEditor::RemoveContainerWithTransaction() failed"); 611 return unwrapStyledElementResult; 612 } 613 614 // Get the default browser background color if we need it for 615 // GetCSSBackgroundColorState 616 617 // static 618 void CSSEditUtils::GetDefaultBackgroundColor(nsAString& aColor) { 619 aColor.AssignLiteral("#ffffff"); // Default to white 620 621 if (MOZ_UNLIKELY(StaticPrefs::editor_use_custom_colors())) { 622 DebugOnly<nsresult> rv = 623 Preferences::GetString("editor.background_color", aColor); 624 // XXX Why don't you validate the pref value? 625 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 626 "failed to get editor.background_color"); 627 return; 628 } 629 630 if (StaticPrefs::browser_display_document_color_use() != 2) { 631 return; 632 } 633 634 DebugOnly<nsresult> rv = 635 Preferences::GetString("browser.display.background_color", aColor); 636 // XXX Why don't you validate the pref value? 637 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 638 "failed to get browser.display.background_color"); 639 } 640 641 // static 642 void CSSEditUtils::ParseLength(const nsAString& aString, float* aValue, 643 nsAtom** aUnit) { 644 if (aString.IsEmpty()) { 645 *aValue = 0; 646 *aUnit = NS_Atomize(aString).take(); 647 return; 648 } 649 650 nsAString::const_iterator iter; 651 aString.BeginReading(iter); 652 653 float a = 10.0f, b = 1.0f, value = 0; 654 int8_t sign = 1; 655 int32_t i = 0, j = aString.Length(); 656 char16_t c; 657 bool floatingPointFound = false; 658 c = *iter; 659 if (char16_t('-') == c) { 660 sign = -1; 661 iter++; 662 i++; 663 } else if (char16_t('+') == c) { 664 iter++; 665 i++; 666 } 667 while (i < j) { 668 c = *iter; 669 if ((char16_t('0') == c) || (char16_t('1') == c) || (char16_t('2') == c) || 670 (char16_t('3') == c) || (char16_t('4') == c) || (char16_t('5') == c) || 671 (char16_t('6') == c) || (char16_t('7') == c) || (char16_t('8') == c) || 672 (char16_t('9') == c)) { 673 value = (value * a) + (b * (c - char16_t('0'))); 674 b = b / 10 * a; 675 } else if (!floatingPointFound && (char16_t('.') == c)) { 676 floatingPointFound = true; 677 a = 1.0f; 678 b = 0.1f; 679 } else 680 break; 681 iter++; 682 i++; 683 } 684 *aValue = value * sign; 685 *aUnit = NS_Atomize(StringTail(aString, j - i)).take(); 686 } 687 688 // static 689 nsStaticAtom* CSSEditUtils::GetCSSPropertyAtom( 690 nsCSSEditableProperty aProperty) { 691 switch (aProperty) { 692 case eCSSEditableProperty_background_color: 693 return nsGkAtoms::background_color; 694 case eCSSEditableProperty_background_image: 695 return nsGkAtoms::background_image; 696 case eCSSEditableProperty_border: 697 return nsGkAtoms::border; 698 case eCSSEditableProperty_caption_side: 699 return nsGkAtoms::caption_side; 700 case eCSSEditableProperty_color: 701 return nsGkAtoms::color; 702 case eCSSEditableProperty_float: 703 return nsGkAtoms::_float; 704 case eCSSEditableProperty_font_family: 705 return nsGkAtoms::font_family; 706 case eCSSEditableProperty_font_size: 707 return nsGkAtoms::font_size; 708 case eCSSEditableProperty_font_style: 709 return nsGkAtoms::font_style; 710 case eCSSEditableProperty_font_weight: 711 return nsGkAtoms::font_weight; 712 case eCSSEditableProperty_height: 713 return nsGkAtoms::height; 714 case eCSSEditableProperty_list_style_type: 715 return nsGkAtoms::list_style_type; 716 case eCSSEditableProperty_margin_left: 717 return nsGkAtoms::marginLeft; 718 case eCSSEditableProperty_margin_right: 719 return nsGkAtoms::marginRight; 720 case eCSSEditableProperty_text_align: 721 return nsGkAtoms::textAlign; 722 case eCSSEditableProperty_text_decoration: 723 return nsGkAtoms::text_decoration; 724 case eCSSEditableProperty_vertical_align: 725 return nsGkAtoms::vertical_align; 726 case eCSSEditableProperty_whitespace: 727 return nsGkAtoms::white_space; 728 case eCSSEditableProperty_width: 729 return nsGkAtoms::width; 730 case eCSSEditableProperty_NONE: 731 // intentionally empty 732 return nullptr; 733 } 734 MOZ_ASSERT_UNREACHABLE("Got unknown property"); 735 return nullptr; 736 } 737 738 // static 739 void CSSEditUtils::GetCSSDeclarations( 740 const CSSEquivTable* aEquivTable, const nsAString* aValue, 741 HandlingFor aHandlingFor, nsTArray<CSSDeclaration>& aOutCSSDeclarations) { 742 // clear arrays 743 aOutCSSDeclarations.Clear(); 744 745 // if we have an input value, let's use it 746 nsAutoString value, lowerCasedValue; 747 if (aValue) { 748 value.Assign(*aValue); 749 lowerCasedValue.Assign(*aValue); 750 ToLowerCase(lowerCasedValue); 751 } 752 753 for (size_t index = 0;; index++) { 754 const nsCSSEditableProperty cssProperty = aEquivTable[index].cssProperty; 755 if (!cssProperty) { 756 break; 757 } 758 if (aHandlingFor == HandlingFor::SettingStyle || 759 aEquivTable[index].gettable) { 760 nsAutoString cssValue, cssPropertyString; 761 // find the equivalent css value for the index-th property in 762 // the equivalence table 763 (*aEquivTable[index].processValueFunctor)( 764 (aHandlingFor == HandlingFor::SettingStyle || 765 aEquivTable[index].caseSensitiveValue) 766 ? &value 767 : &lowerCasedValue, 768 cssValue, aEquivTable[index].defaultValue, 769 aEquivTable[index].prependValue, aEquivTable[index].appendValue); 770 nsStaticAtom* const propertyAtom = GetCSSPropertyAtom(cssProperty); 771 if (MOZ_LIKELY(propertyAtom)) { 772 aOutCSSDeclarations.AppendElement( 773 CSSDeclaration{*propertyAtom, cssValue}); 774 } 775 } 776 } 777 } 778 779 // static 780 void CSSEditUtils::GetCSSDeclarations( 781 Element& aElement, const EditorElementStyle& aStyle, 782 const nsAString* aValue, HandlingFor aHandlingFor, 783 nsTArray<CSSDeclaration>& aOutCSSDeclarations) { 784 nsStaticAtom* const htmlProperty = 785 aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mHTMLProperty : nullptr; 786 const RefPtr<nsAtom> attributeOrStyle = 787 aStyle.IsInlineStyle() ? aStyle.AsInlineStyle().mAttribute 788 : aStyle.Style(); 789 790 const auto* equivTable = [&]() -> const CSSEditUtils::CSSEquivTable* { 791 if (nsGkAtoms::b == htmlProperty) { 792 return boldEquivTable; 793 } 794 if (nsGkAtoms::i == htmlProperty) { 795 return italicEquivTable; 796 } 797 if (nsGkAtoms::u == htmlProperty) { 798 return underlineEquivTable; 799 } 800 if (nsGkAtoms::strike == htmlProperty) { 801 return strikeEquivTable; 802 } 803 if (nsGkAtoms::tt == htmlProperty) { 804 return ttEquivTable; 805 } 806 if (!attributeOrStyle) { 807 return nullptr; 808 } 809 if (nsGkAtoms::font == htmlProperty) { 810 if (attributeOrStyle == nsGkAtoms::color) { 811 return fontColorEquivTable; 812 } 813 if (attributeOrStyle == nsGkAtoms::face) { 814 return fontFaceEquivTable; 815 } 816 if (attributeOrStyle == nsGkAtoms::size) { 817 return fontSizeEquivTable; 818 } 819 MOZ_ASSERT(attributeOrStyle == nsGkAtoms::bgcolor); 820 } 821 if (attributeOrStyle == nsGkAtoms::bgcolor) { 822 return bgcolorEquivTable; 823 } 824 if (attributeOrStyle == nsGkAtoms::background) { 825 return backgroundImageEquivTable; 826 } 827 if (attributeOrStyle == nsGkAtoms::text) { 828 return textColorEquivTable; 829 } 830 if (attributeOrStyle == nsGkAtoms::border) { 831 return borderEquivTable; 832 } 833 if (attributeOrStyle == nsGkAtoms::align) { 834 if (aElement.IsHTMLElement(nsGkAtoms::table)) { 835 return tableAlignEquivTable; 836 } 837 if (aElement.IsHTMLElement(nsGkAtoms::hr)) { 838 return hrAlignEquivTable; 839 } 840 if (aElement.IsAnyOfHTMLElements(nsGkAtoms::legend, nsGkAtoms::caption)) { 841 return captionAlignEquivTable; 842 } 843 return textAlignEquivTable; 844 } 845 if (attributeOrStyle == nsGkAtoms::valign) { 846 return verticalAlignEquivTable; 847 } 848 if (attributeOrStyle == nsGkAtoms::nowrap) { 849 return nowrapEquivTable; 850 } 851 if (attributeOrStyle == nsGkAtoms::width) { 852 return widthEquivTable; 853 } 854 if (attributeOrStyle == nsGkAtoms::height || 855 (aElement.IsHTMLElement(nsGkAtoms::hr) && 856 attributeOrStyle == nsGkAtoms::size)) { 857 return heightEquivTable; 858 } 859 if (attributeOrStyle == nsGkAtoms::type && 860 aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul, 861 nsGkAtoms::li)) { 862 return listStyleTypeEquivTable; 863 } 864 return nullptr; 865 }(); 866 if (equivTable) { 867 GetCSSDeclarations(equivTable, aValue, aHandlingFor, aOutCSSDeclarations); 868 } 869 } 870 871 // Add to aNode the CSS inline style equivalent to HTMLProperty/aAttribute/ 872 // aValue for the node, and return in aCount the number of CSS properties set 873 // by the call. The Element version returns aCount instead. 874 Result<size_t, nsresult> CSSEditUtils::SetCSSEquivalentToStyle( 875 WithTransaction aWithTransaction, HTMLEditor& aHTMLEditor, 876 nsStyledElement& aStyledElement, const EditorElementStyle& aStyleToSet, 877 const nsAString* aValue) { 878 MOZ_DIAGNOSTIC_ASSERT(aStyleToSet.IsCSSSettable(aStyledElement)); 879 880 // we can apply the styles only if the node is an element and if we have 881 // an equivalence for the requested HTML style in this implementation 882 883 // Find the CSS equivalence to the HTML style 884 AutoTArray<CSSDeclaration, 4> cssDeclarations; 885 GetCSSDeclarations(aStyledElement, aStyleToSet, aValue, 886 HandlingFor::SettingStyle, cssDeclarations); 887 888 // set the individual CSS inline styles 889 for (const CSSDeclaration& cssDeclaration : cssDeclarations) { 890 nsresult rv = SetCSSPropertyInternal( 891 aHTMLEditor, aStyledElement, MOZ_KnownLive(cssDeclaration.mProperty), 892 cssDeclaration.mValue, aWithTransaction == WithTransaction::No); 893 if (NS_FAILED(rv)) { 894 NS_WARNING("CSSEditUtils::SetCSSPropertyInternal() failed"); 895 return Err(rv); 896 } 897 } 898 return cssDeclarations.Length(); 899 } 900 901 // static 902 nsresult CSSEditUtils::RemoveCSSEquivalentToStyle( 903 WithTransaction aWithTransaction, HTMLEditor& aHTMLEditor, 904 nsStyledElement& aStyledElement, const EditorElementStyle& aStyleToRemove, 905 const nsAString* aValue) { 906 MOZ_DIAGNOSTIC_ASSERT(aStyleToRemove.IsCSSRemovable(aStyledElement)); 907 908 // we can apply the styles only if the node is an element and if we have 909 // an equivalence for the requested HTML style in this implementation 910 911 // Find the CSS equivalence to the HTML style 912 AutoTArray<CSSDeclaration, 4> cssDeclarations; 913 GetCSSDeclarations(aStyledElement, aStyleToRemove, aValue, 914 HandlingFor::RemovingStyle, cssDeclarations); 915 916 // remove the individual CSS inline styles 917 for (const CSSDeclaration& cssDeclaration : cssDeclarations) { 918 nsresult rv = RemoveCSSPropertyInternal( 919 aHTMLEditor, aStyledElement, MOZ_KnownLive(cssDeclaration.mProperty), 920 cssDeclaration.mValue, aWithTransaction == WithTransaction::No); 921 if (NS_FAILED(rv)) { 922 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithoutTransaction() failed"); 923 return rv; 924 } 925 } 926 return NS_OK; 927 } 928 929 // static 930 nsresult CSSEditUtils::GetComputedCSSEquivalentTo( 931 Element& aElement, const EditorElementStyle& aStyle, nsAString& aOutValue) { 932 return GetCSSEquivalentTo(aElement, aStyle, aOutValue, StyleType::Computed); 933 } 934 935 // static 936 nsresult CSSEditUtils::GetCSSEquivalentTo(Element& aElement, 937 const EditorElementStyle& aStyle, 938 nsAString& aOutValue, 939 StyleType aStyleType) { 940 MOZ_ASSERT_IF(aStyle.IsInlineStyle(), 941 !aStyle.AsInlineStyle().IsStyleToClearAllInlineStyles()); 942 MOZ_DIAGNOSTIC_ASSERT(aStyle.IsCSSSettable(aElement) || 943 aStyle.IsCSSRemovable(aElement)); 944 945 aOutValue.Truncate(); 946 AutoTArray<CSSDeclaration, 4> cssDeclarations; 947 GetCSSDeclarations(aElement, aStyle, nullptr, HandlingFor::GettingStyle, 948 cssDeclarations); 949 nsAutoString valueString; 950 for (const CSSDeclaration& cssDeclaration : cssDeclarations) { 951 valueString.Truncate(); 952 // retrieve the specified/computed value of the property 953 if (aStyleType == StyleType::Computed) { 954 nsresult rv = GetComputedCSSInlinePropertyBase( 955 aElement, MOZ_KnownLive(cssDeclaration.mProperty), valueString); 956 if (NS_FAILED(rv)) { 957 NS_WARNING("CSSEditUtils::GetComputedCSSInlinePropertyBase() failed"); 958 return rv; 959 } 960 } else { 961 nsresult rv = GetSpecifiedCSSInlinePropertyBase( 962 aElement, cssDeclaration.mProperty, valueString); 963 if (NS_FAILED(rv)) { 964 NS_WARNING("CSSEditUtils::GetSpecifiedCSSInlinePropertyBase() failed"); 965 return rv; 966 } 967 } 968 // append the value to aOutValue (possibly with a leading white-space) 969 if (!aOutValue.IsEmpty()) { 970 aOutValue.Append(HTMLEditUtils::kSpace); 971 } 972 aOutValue.Append(valueString); 973 } 974 return NS_OK; 975 } 976 977 // Does the node aContent (or its parent, if it's not an element node) have a 978 // CSS style equivalent to the HTML style 979 // aHTMLProperty/aAttribute/valueString? The value of aStyleType controls 980 // the styles we retrieve: specified or computed. The return value aIsSet is 981 // true if the CSS styles are set. 982 // 983 // The nsIContent variant returns aIsSet instead of using an out parameter, and 984 // does not modify aValue. 985 986 // static 987 Result<bool, nsresult> CSSEditUtils::IsComputedCSSEquivalentTo( 988 const HTMLEditor& aHTMLEditor, nsIContent& aContent, 989 const EditorInlineStyle& aStyle, nsAString& aInOutValue) { 990 return IsCSSEquivalentTo(aHTMLEditor, aContent, aStyle, aInOutValue, 991 StyleType::Computed); 992 } 993 994 // static 995 Result<bool, nsresult> CSSEditUtils::IsSpecifiedCSSEquivalentTo( 996 const HTMLEditor& aHTMLEditor, nsIContent& aContent, 997 const EditorInlineStyle& aStyle, nsAString& aInOutValue) { 998 return IsCSSEquivalentTo(aHTMLEditor, aContent, aStyle, aInOutValue, 999 StyleType::Specified); 1000 } 1001 1002 // static 1003 Result<bool, nsresult> CSSEditUtils::IsCSSEquivalentTo( 1004 const HTMLEditor& aHTMLEditor, nsIContent& aContent, 1005 const EditorInlineStyle& aStyle, nsAString& aInOutValue, 1006 StyleType aStyleType) { 1007 MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles()); 1008 1009 nsAutoString htmlValueString(aInOutValue); 1010 bool isSet = false; 1011 // FYI: Cannot use InclusiveAncestorsOfType here because 1012 // GetCSSEquivalentTo() may flush pending notifications. 1013 for (RefPtr<Element> element = aContent.GetAsElementOrParentElement(); 1014 element; element = element->GetParentElement()) { 1015 nsCOMPtr<nsINode> parentNode = element->GetParentNode(); 1016 aInOutValue.Assign(htmlValueString); 1017 // get the value of the CSS equivalent styles 1018 nsresult rv = GetCSSEquivalentTo(*element, aStyle, aInOutValue, aStyleType); 1019 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 1020 return Err(NS_ERROR_EDITOR_DESTROYED); 1021 } 1022 if (NS_FAILED(rv)) { 1023 NS_WARNING( 1024 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() " 1025 "failed"); 1026 return Err(rv); 1027 } 1028 if (NS_WARN_IF(parentNode != element->GetParentNode())) { 1029 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1030 } 1031 1032 // early way out if we can 1033 if (aInOutValue.IsEmpty()) { 1034 return isSet; 1035 } 1036 1037 if (nsGkAtoms::b == aStyle.mHTMLProperty) { 1038 if (aInOutValue.EqualsLiteral("bold")) { 1039 isSet = true; 1040 } else if (aInOutValue.EqualsLiteral("normal")) { 1041 isSet = false; 1042 } else if (aInOutValue.EqualsLiteral("bolder")) { 1043 isSet = true; 1044 aInOutValue.AssignLiteral("bold"); 1045 } else { 1046 int32_t weight = 0; 1047 nsresult rvIgnored; 1048 nsAutoString value(aInOutValue); 1049 weight = value.ToInteger(&rvIgnored); 1050 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1051 "nsAString::ToInteger() failed, but ignored"); 1052 if (400 < weight) { 1053 isSet = true; 1054 aInOutValue.AssignLiteral(u"bold"); 1055 } else { 1056 isSet = false; 1057 aInOutValue.AssignLiteral(u"normal"); 1058 } 1059 } 1060 } else if (nsGkAtoms::i == aStyle.mHTMLProperty) { 1061 if (aInOutValue.EqualsLiteral(u"italic") || 1062 aInOutValue.EqualsLiteral(u"oblique")) { 1063 isSet = true; 1064 } 1065 } else if (nsGkAtoms::u == aStyle.mHTMLProperty) { 1066 isSet = ChangeStyleTransaction::ValueIncludes( 1067 NS_ConvertUTF16toUTF8(aInOutValue), "underline"_ns); 1068 } else if (nsGkAtoms::strike == aStyle.mHTMLProperty) { 1069 isSet = ChangeStyleTransaction::ValueIncludes( 1070 NS_ConvertUTF16toUTF8(aInOutValue), "line-through"_ns); 1071 } else if ((nsGkAtoms::font == aStyle.mHTMLProperty && 1072 aStyle.mAttribute == nsGkAtoms::color) || 1073 aStyle.mAttribute == nsGkAtoms::bgcolor) { 1074 isSet = htmlValueString.IsEmpty() || 1075 HTMLEditUtils::IsSameCSSColorValue(htmlValueString, aInOutValue); 1076 } else if (nsGkAtoms::tt == aStyle.mHTMLProperty) { 1077 isSet = StringBeginsWith(aInOutValue, u"monospace"_ns); 1078 } else if (nsGkAtoms::font == aStyle.mHTMLProperty && 1079 aStyle.mAttribute == nsGkAtoms::face) { 1080 if (!htmlValueString.IsEmpty()) { 1081 const char16_t commaSpace[] = {char16_t(','), HTMLEditUtils::kSpace, 0}; 1082 const char16_t comma[] = {char16_t(','), 0}; 1083 htmlValueString.ReplaceSubstring(commaSpace, comma); 1084 nsAutoString valueStringNorm(aInOutValue); 1085 valueStringNorm.ReplaceSubstring(commaSpace, comma); 1086 isSet = htmlValueString.Equals(valueStringNorm, 1087 nsCaseInsensitiveStringComparator); 1088 } else { 1089 isSet = true; 1090 } 1091 return isSet; 1092 } else if (aStyle.IsStyleOfFontSize()) { 1093 if (htmlValueString.IsEmpty()) { 1094 return true; 1095 } 1096 switch (nsContentUtils::ParseLegacyFontSize(htmlValueString)) { 1097 case 1: 1098 return aInOutValue.EqualsLiteral("x-small"); 1099 case 2: 1100 return aInOutValue.EqualsLiteral("small"); 1101 case 3: 1102 return aInOutValue.EqualsLiteral("medium"); 1103 case 4: 1104 return aInOutValue.EqualsLiteral("large"); 1105 case 5: 1106 return aInOutValue.EqualsLiteral("x-large"); 1107 case 6: 1108 return aInOutValue.EqualsLiteral("xx-large"); 1109 case 7: 1110 return aInOutValue.EqualsLiteral("xxx-large"); 1111 } 1112 return false; 1113 } else if (aStyle.mAttribute == nsGkAtoms::align) { 1114 isSet = true; 1115 } else { 1116 return false; 1117 } 1118 1119 if (!htmlValueString.IsEmpty() && 1120 htmlValueString.Equals(aInOutValue, 1121 nsCaseInsensitiveStringComparator)) { 1122 isSet = true; 1123 } 1124 1125 if (htmlValueString.EqualsLiteral(u"-moz-editor-invert-value")) { 1126 isSet = !isSet; 1127 } 1128 1129 if (isSet) { 1130 return true; 1131 } 1132 1133 if (!aStyle.IsStyleOfTextDecoration( 1134 EditorInlineStyle::IgnoreSElement::Yes)) { 1135 return isSet; 1136 } 1137 1138 // Unfortunately, the value of the text-decoration property is not 1139 // inherited. that means that we have to look at ancestors of node to see 1140 // if they are underlined. 1141 } 1142 return isSet; 1143 } 1144 1145 // static 1146 Result<bool, nsresult> CSSEditUtils::HaveComputedCSSEquivalentStyles( 1147 const HTMLEditor& aHTMLEditor, nsIContent& aContent, 1148 const EditorInlineStyle& aStyle) { 1149 return HaveCSSEquivalentStyles(aHTMLEditor, aContent, aStyle, 1150 StyleType::Computed); 1151 } 1152 1153 // static 1154 Result<bool, nsresult> CSSEditUtils::HaveSpecifiedCSSEquivalentStyles( 1155 const HTMLEditor& aHTMLEditor, nsIContent& aContent, 1156 const EditorInlineStyle& aStyle) { 1157 return HaveCSSEquivalentStyles(aHTMLEditor, aContent, aStyle, 1158 StyleType::Specified); 1159 } 1160 1161 // static 1162 Result<bool, nsresult> CSSEditUtils::HaveCSSEquivalentStyles( 1163 const HTMLEditor& aHTMLEditor, nsIContent& aContent, 1164 const EditorInlineStyle& aStyle, StyleType aStyleType) { 1165 MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles()); 1166 1167 // FYI: Unfortunately, we cannot use InclusiveAncestorsOfType here 1168 // because GetCSSEquivalentTo() may flush pending notifications. 1169 nsAutoString valueString; 1170 for (RefPtr<Element> element = aContent.GetAsElementOrParentElement(); 1171 element; element = element->GetParentElement()) { 1172 nsCOMPtr<nsINode> parentNode = element->GetParentNode(); 1173 // get the value of the CSS equivalent styles 1174 nsresult rv = GetCSSEquivalentTo(*element, aStyle, valueString, aStyleType); 1175 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 1176 return Err(NS_ERROR_EDITOR_DESTROYED); 1177 } 1178 if (NS_FAILED(rv)) { 1179 NS_WARNING( 1180 "CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSetInternal() " 1181 "failed"); 1182 return Err(rv); 1183 } 1184 if (NS_WARN_IF(parentNode != element->GetParentNode())) { 1185 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1186 } 1187 1188 if (!valueString.IsEmpty()) { 1189 return true; 1190 } 1191 1192 if (!aStyle.IsStyleOfTextDecoration( 1193 EditorInlineStyle::IgnoreSElement::Yes)) { 1194 return false; 1195 } 1196 1197 // Unfortunately, the value of the text-decoration property is not 1198 // inherited. 1199 // that means that we have to look at ancestors of node to see if they 1200 // are underlined. 1201 } 1202 1203 return false; 1204 } 1205 1206 // ElementsSameStyle compares two elements and checks if they have the same 1207 // specified CSS declarations in the STYLE attribute 1208 // The answer is always negative if at least one of them carries an ID or a 1209 // class 1210 1211 // static 1212 bool CSSEditUtils::DoStyledElementsHaveSameStyle( 1213 nsStyledElement& aStyledElement, nsStyledElement& aOtherStyledElement) { 1214 if (aStyledElement.HasAttr(nsGkAtoms::id) || 1215 aOtherStyledElement.HasAttr(nsGkAtoms::id)) { 1216 // at least one of the spans carries an ID ; suspect a CSS rule applies to 1217 // it and refuse to merge the nodes 1218 return false; 1219 } 1220 1221 nsAutoString firstClass, otherClass; 1222 bool isElementClassSet = 1223 aStyledElement.GetAttr(nsGkAtoms::_class, firstClass); 1224 bool isOtherElementClassSet = aOtherStyledElement.GetAttr( 1225 kNameSpaceID_None, nsGkAtoms::_class, otherClass); 1226 if (isElementClassSet && isOtherElementClassSet) { 1227 // both spans carry a class, let's compare them 1228 if (!firstClass.Equals(otherClass)) { 1229 // WARNING : technically, the comparison just above is questionable : 1230 // from a pure HTML/CSS point of view class="a b" is NOT the same than 1231 // class="b a" because a CSS rule could test the exact value of the class 1232 // attribute to be "a b" for instance ; from a user's point of view, a 1233 // wysiwyg editor should probably NOT make any difference. CSS people 1234 // need to discuss this issue before any modification. 1235 return false; 1236 } 1237 } else if (isElementClassSet || isOtherElementClassSet) { 1238 // one span only carries a class, early way out 1239 return false; 1240 } 1241 1242 // XXX If `GetPropertyValue()` won't run script, we can stop using 1243 // nsCOMPtr here. 1244 nsCOMPtr<nsICSSDeclaration> firstCSSDecl = aStyledElement.Style(); 1245 if (!firstCSSDecl) { 1246 NS_WARNING("nsStyledElement::Style() failed"); 1247 return false; 1248 } 1249 nsCOMPtr<nsICSSDeclaration> otherCSSDecl = aOtherStyledElement.Style(); 1250 if (!otherCSSDecl) { 1251 NS_WARNING("nsStyledElement::Style() failed"); 1252 return false; 1253 } 1254 1255 const uint32_t firstLength = firstCSSDecl->Length(); 1256 const uint32_t otherLength = otherCSSDecl->Length(); 1257 if (firstLength != otherLength) { 1258 // early way out if we can 1259 return false; 1260 } 1261 1262 if (!firstLength) { 1263 // no inline style ! 1264 return true; 1265 } 1266 1267 for (uint32_t i = 0; i < firstLength; i++) { 1268 nsAutoCString firstValue, otherValue; 1269 nsAutoCString propertyNameString; 1270 firstCSSDecl->Item(i, propertyNameString); 1271 firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); 1272 otherCSSDecl->GetPropertyValue(propertyNameString, otherValue); 1273 // FIXME: We need to handle all properties whose values are color. 1274 // However, it's too expensive if we keep using string property names. 1275 if (propertyNameString.EqualsLiteral("color") || 1276 propertyNameString.EqualsLiteral("background-color")) { 1277 if (!HTMLEditUtils::IsSameCSSColorValue(firstValue, otherValue)) { 1278 return false; 1279 } 1280 } else if (!firstValue.Equals(otherValue)) { 1281 return false; 1282 } 1283 } 1284 for (uint32_t i = 0; i < otherLength; i++) { 1285 nsAutoCString firstValue, otherValue; 1286 nsAutoCString propertyNameString; 1287 otherCSSDecl->Item(i, propertyNameString); 1288 otherCSSDecl->GetPropertyValue(propertyNameString, otherValue); 1289 firstCSSDecl->GetPropertyValue(propertyNameString, firstValue); 1290 // FIXME: We need to handle all properties whose values are color. 1291 // However, it's too expensive if we keep using string property names. 1292 if (propertyNameString.EqualsLiteral("color") || 1293 propertyNameString.EqualsLiteral("background-color")) { 1294 if (!HTMLEditUtils::IsSameCSSColorValue(firstValue, otherValue)) { 1295 return false; 1296 } 1297 } else if (!firstValue.Equals(otherValue)) { 1298 return false; 1299 } 1300 } 1301 1302 return true; 1303 } 1304 1305 } // namespace mozilla