tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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