tor-browser

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

ChangeStyleTransaction.cpp (13474B)


      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 "ChangeStyleTransaction.h"
      7 
      8 #include "EditorDOMAPIWrapper.h"
      9 #include "HTMLEditor.h"
     10 #include "HTMLEditUtils.h"
     11 #include "mozilla/Logging.h"
     12 #include "mozilla/ToString.h"
     13 #include "mozilla/dom/Element.h"  // for Element
     14 #include "nsAString.h"            // for nsAString::Append, etc.
     15 #include "nsCRT.h"                // for nsCRT::IsAsciiSpace
     16 #include "nsDebug.h"              // for NS_WARNING, etc.
     17 #include "nsError.h"              // for NS_ERROR_NULL_POINTER, etc.
     18 #include "nsGkAtoms.h"            // for nsGkAtoms, etc.
     19 #include "nsICSSDeclaration.h"    // for nsICSSDeclaration.
     20 #include "nsLiteralString.h"      // for NS_LITERAL_STRING, etc.
     21 #include "nsReadableUtils.h"      // for ToNewUnicode
     22 #include "nsString.h"             // for nsAutoString, nsString, etc.
     23 #include "nsStyledElement.h"      // for nsStyledElement.
     24 #include "nsUnicharUtils.h"       // for nsCaseInsensitiveStringComparator
     25 
     26 namespace mozilla {
     27 
     28 using namespace dom;
     29 
     30 // static
     31 already_AddRefed<ChangeStyleTransaction> ChangeStyleTransaction::Create(
     32    HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty,
     33    const nsAString& aValue) {
     34  RefPtr<ChangeStyleTransaction> transaction = new ChangeStyleTransaction(
     35      aHTMLEditor, aStyledElement, aProperty, aValue, false);
     36  return transaction.forget();
     37 }
     38 
     39 // static
     40 already_AddRefed<ChangeStyleTransaction> ChangeStyleTransaction::CreateToRemove(
     41    HTMLEditor& aHTMLEditor, nsStyledElement& aStyledElement, nsAtom& aProperty,
     42    const nsAString& aValue) {
     43  RefPtr<ChangeStyleTransaction> transaction = new ChangeStyleTransaction(
     44      aHTMLEditor, aStyledElement, aProperty, aValue, true);
     45  return transaction.forget();
     46 }
     47 
     48 ChangeStyleTransaction::ChangeStyleTransaction(HTMLEditor& aHTMLEditor,
     49                                               nsStyledElement& aStyledElement,
     50                                               nsAtom& aProperty,
     51                                               const nsAString& aValue,
     52                                               bool aRemove)
     53    : EditTransactionBase(),
     54      mHTMLEditor(&aHTMLEditor),
     55      mStyledElement(&aStyledElement),
     56      mProperty(&aProperty),
     57      mRemoveProperty(aRemove),
     58      mUndoAttributeWasSet(false),
     59      mRedoAttributeWasSet(false) {
     60  CopyUTF16toUTF8(aValue, mValue);
     61 }
     62 
     63 std::ostream& operator<<(std::ostream& aStream,
     64                         const ChangeStyleTransaction& aTransaction) {
     65  aStream << "{ mStyledElement=" << aTransaction.mStyledElement.get();
     66  if (aTransaction.mStyledElement) {
     67    aStream << " (" << *aTransaction.mStyledElement << ")";
     68  }
     69  aStream << ", mProperty=" << nsAtomCString(aTransaction.mProperty).get()
     70          << ", mValue=\"" << aTransaction.mValue.get() << "\", mUndoValue=\""
     71          << aTransaction.mUndoValue.get()
     72          << "\", mRedoValue=" << aTransaction.mRedoValue.get()
     73          << ", mRemoveProperty="
     74          << (aTransaction.mRemoveProperty ? "true" : "false")
     75          << ", mUndoAttributeWasSet="
     76          << (aTransaction.mUndoAttributeWasSet ? "true" : "false")
     77          << ", mRedoAttributeWasSet="
     78          << (aTransaction.mRedoAttributeWasSet ? "true" : "false")
     79          << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }";
     80  return aStream;
     81 }
     82 
     83 #define kNullCh ('\0')
     84 
     85 NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase,
     86                                   mHTMLEditor, mStyledElement)
     87 
     88 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction)
     89 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
     90 
     91 NS_IMPL_ADDREF_INHERITED(ChangeStyleTransaction, EditTransactionBase)
     92 NS_IMPL_RELEASE_INHERITED(ChangeStyleTransaction, EditTransactionBase)
     93 
     94 // Answers true if aValue is in the string list of white-space separated values
     95 // aValueList.
     96 bool ChangeStyleTransaction::ValueIncludes(const nsACString& aValueList,
     97                                           const nsACString& aValue) {
     98  nsAutoCString valueList(aValueList);
     99  bool result = false;
    100 
    101  // put an extra null at the end
    102  valueList.Append(kNullCh);
    103 
    104  char* start = valueList.BeginWriting();
    105  char* end = start;
    106 
    107  while (kNullCh != *start) {
    108    while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
    109      // skip leading space
    110      start++;
    111    }
    112    end = start;
    113 
    114    while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
    115      // look for space or end
    116      end++;
    117    }
    118    // end string here
    119    *end = kNullCh;
    120 
    121    if (start < end) {
    122      if (aValue.Equals(nsDependentCString(start),
    123                        nsCaseInsensitiveCStringComparator)) {
    124        result = true;
    125        break;
    126      }
    127    }
    128    start = ++end;
    129  }
    130  return result;
    131 }
    132 
    133 NS_IMETHODIMP ChangeStyleTransaction::DoTransaction() {
    134  MOZ_LOG(GetLogModule(), LogLevel::Info,
    135          ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
    136           ToString(*this).c_str()));
    137 
    138  MOZ_ASSERT(mHTMLEditor);
    139  if (NS_WARN_IF(!mStyledElement)) {
    140    return NS_ERROR_NOT_AVAILABLE;
    141  }
    142 
    143  const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    144  const OwningNonNull<nsStyledElement> styledElement = *mStyledElement;
    145  const nsCOMPtr<nsDOMCSSDeclaration> cssDecl = styledElement->Style();
    146 
    147  // FIXME(bug 1606994): Using atoms forces a string copy here which is not
    148  // great.
    149  nsAutoCString propertyNameString;
    150  mProperty->ToUTF8String(propertyNameString);
    151 
    152  mUndoAttributeWasSet = mStyledElement->HasAttr(nsGkAtoms::style);
    153 
    154  nsAutoCString values;
    155  cssDecl->GetPropertyValue(propertyNameString, values);
    156  mUndoValue.Assign(values);
    157 
    158  if (mRemoveProperty) {
    159    if (mProperty == nsGkAtoms::text_decoration) {
    160      BuildTextDecorationValueToRemove(values, mValue, values);
    161      if (values.IsEmpty()) {
    162        nsresult rv =
    163            AutoCSSDeclarationAPIWrapper(htmlEditor, styledElement, cssDecl)
    164                .RemoveProperty(propertyNameString);
    165        if (NS_FAILED(rv)) {
    166          NS_WARNING("AutoCSSDeclarationAPIWrapper::RemoveProperty() failed");
    167          return rv;
    168        }
    169      } else {
    170        nsAutoCString priority;
    171        cssDecl->GetPropertyPriority(propertyNameString, priority);
    172        nsresult rv =
    173            AutoCSSDeclarationAPIWrapper(htmlEditor, styledElement, cssDecl)
    174                .SetProperty(propertyNameString, values, priority);
    175        if (NS_FAILED(rv)) {
    176          NS_WARNING("AutoCSSDeclarationAPIWrapper::SetProperty() failed");
    177          return rv;
    178        }
    179      }
    180    } else {
    181      nsresult rv =
    182          AutoCSSDeclarationAPIWrapper(htmlEditor, styledElement, cssDecl)
    183              .RemoveProperty(propertyNameString);
    184      if (NS_FAILED(rv)) {
    185        NS_WARNING("AutoCSSDeclarationAPIWrapper::RemoveProperty() failed");
    186        return rv;
    187      }
    188    }
    189  } else {
    190    nsAutoCString priority;
    191    cssDecl->GetPropertyPriority(propertyNameString, priority);
    192    if (mProperty == nsGkAtoms::text_decoration) {
    193      BuildTextDecorationValueToSet(values, mValue, values);
    194    } else {
    195      values.Assign(mValue);
    196    }
    197    nsresult rv =
    198        AutoCSSDeclarationAPIWrapper(htmlEditor, styledElement, cssDecl)
    199            .SetProperty(propertyNameString, values, priority);
    200    if (NS_FAILED(rv)) {
    201      NS_WARNING("AutoCSSDeclarationAPIWrapper::SetProperty() failed");
    202      return rv;
    203    }
    204  }
    205 
    206  // Let's be sure we don't keep an empty style attribute
    207  uint32_t length = cssDecl->Length();
    208  if (!length) {
    209    AutoElementAttrAPIWrapper elementWrapper(htmlEditor, styledElement);
    210    nsresult rv = elementWrapper.UnsetAttr(nsGkAtoms::style, true);
    211    if (NS_FAILED(rv)) {
    212      NS_WARNING("AutoElementAttrAPIWrapper::UnsetAttr() failed");
    213      return rv;
    214    }
    215    NS_WARNING_ASSERTION(
    216        elementWrapper.IsExpectedResult(EmptyString()),
    217        "Removing style attribute caused other mutations, but ignored");
    218  } else {
    219    mRedoAttributeWasSet = true;
    220  }
    221 
    222  cssDecl->GetPropertyValue(propertyNameString, mRedoValue);
    223  return NS_OK;
    224 }
    225 
    226 nsresult ChangeStyleTransaction::SetStyle(bool aAttributeWasSet,
    227                                          nsACString& aValue) {
    228  if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mStyledElement)) {
    229    return NS_ERROR_NOT_AVAILABLE;
    230  }
    231  const OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
    232  const OwningNonNull<nsStyledElement> styledElement = *mStyledElement;
    233  if (aAttributeWasSet) {
    234    // The style attribute was not empty, let's recreate the declaration
    235    nsAutoCString propertyNameString;
    236    mProperty->ToUTF8String(propertyNameString);
    237 
    238    const nsCOMPtr<nsDOMCSSDeclaration> cssDecl = styledElement->Style();
    239 
    240    ErrorResult error;
    241    if (aValue.IsEmpty()) {
    242      // An empty value means we have to remove the property
    243      nsresult rv =
    244          AutoCSSDeclarationAPIWrapper(htmlEditor, styledElement, cssDecl)
    245              .RemoveProperty(propertyNameString);
    246      if (NS_FAILED(rv)) {
    247        NS_WARNING("AutoCSSDeclarationAPIWrapper::RemoveProperty() failed");
    248        return rv;
    249      }
    250    }
    251    // Let's recreate the declaration as it was
    252    nsAutoCString priority;
    253    cssDecl->GetPropertyPriority(propertyNameString, priority);
    254    nsresult rv =
    255        AutoCSSDeclarationAPIWrapper(htmlEditor, styledElement, cssDecl)
    256            .SetProperty(propertyNameString, aValue, priority);
    257    if (NS_FAILED(rv)) {
    258      NS_WARNING("AutoCSSDeclarationAPIWrapper::SetProperty() failed");
    259      return rv;
    260    }
    261    return NS_OK;
    262  }
    263 
    264  AutoElementAttrAPIWrapper elementWrapper(htmlEditor, styledElement);
    265  nsresult rv = elementWrapper.UnsetAttr(nsGkAtoms::style, true);
    266  if (NS_FAILED(rv)) {
    267    NS_WARNING("AutoElementAttrAPIWrapper::UnsetAttr() failed");
    268    return rv;
    269  }
    270  NS_WARNING_ASSERTION(
    271      elementWrapper.IsExpectedResult(EmptyString()),
    272      "Removing style attribute caused other mutations, but ignored");
    273  return NS_OK;
    274 }
    275 
    276 NS_IMETHODIMP ChangeStyleTransaction::UndoTransaction() {
    277  MOZ_LOG(GetLogModule(), LogLevel::Info,
    278          ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
    279           ToString(*this).c_str()));
    280 
    281  nsresult rv = SetStyle(mUndoAttributeWasSet, mUndoValue);
    282  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    283                       "ChangeStyleTransaction::SetStyle() failed");
    284  return rv;
    285 }
    286 
    287 NS_IMETHODIMP ChangeStyleTransaction::RedoTransaction() {
    288  MOZ_LOG(GetLogModule(), LogLevel::Info,
    289          ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
    290           ToString(*this).c_str()));
    291 
    292  nsresult rv = SetStyle(mRedoAttributeWasSet, mRedoValue);
    293  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    294                       "ChangeStyleTransaction::SetStyle() failed");
    295  return rv;
    296 }
    297 
    298 // static
    299 void ChangeStyleTransaction::BuildTextDecorationValueToSet(
    300    const nsACString& aCurrentValues, const nsACString& aAddingValues,
    301    nsACString& aOutValues) {
    302  const bool underline = ValueIncludes(aCurrentValues, "underline"_ns) ||
    303                         ValueIncludes(aAddingValues, "underline"_ns);
    304  const bool overline = ValueIncludes(aCurrentValues, "overline"_ns) ||
    305                        ValueIncludes(aAddingValues, "overline"_ns);
    306  const bool lineThrough = ValueIncludes(aCurrentValues, "line-through"_ns) ||
    307                           ValueIncludes(aAddingValues, "line-through"_ns);
    308  // FYI: Don't refer aCurrentValues which may refer same instance as
    309  // aOutValues.
    310  BuildTextDecorationValue(underline, overline, lineThrough, aOutValues);
    311 }
    312 
    313 // static
    314 void ChangeStyleTransaction::BuildTextDecorationValueToRemove(
    315    const nsACString& aCurrentValues, const nsACString& aRemovingValues,
    316    nsACString& aOutValues) {
    317  const bool underline = ValueIncludes(aCurrentValues, "underline"_ns) &&
    318                         !ValueIncludes(aRemovingValues, "underline"_ns);
    319  const bool overline = ValueIncludes(aCurrentValues, "overline"_ns) &&
    320                        !ValueIncludes(aRemovingValues, "overline"_ns);
    321  const bool lineThrough = ValueIncludes(aCurrentValues, "line-through"_ns) &&
    322                           !ValueIncludes(aRemovingValues, "line-through"_ns);
    323  // FYI: Don't refer aCurrentValues which may refer same instance as
    324  // aOutValues.
    325  BuildTextDecorationValue(underline, overline, lineThrough, aOutValues);
    326 }
    327 
    328 void ChangeStyleTransaction::BuildTextDecorationValue(bool aUnderline,
    329                                                      bool aOverline,
    330                                                      bool aLineThrough,
    331                                                      nsACString& aOutValues) {
    332  // We should build text-decoration(-line) value as same as Blink for
    333  // compatibility.  Blink sets text-decoration-line to the values in the
    334  // following order.  Blink drops `blink` and other styles like color and
    335  // style.  For keeping the code simple, let's use the lossy behavior.
    336  aOutValues.Truncate();
    337  if (aUnderline) {
    338    aOutValues.AssignLiteral("underline");
    339  }
    340  if (aOverline) {
    341    if (!aOutValues.IsEmpty()) {
    342      aOutValues.Append(HTMLEditUtils::kSpace);
    343    }
    344    aOutValues.AppendLiteral("overline");
    345  }
    346  if (aLineThrough) {
    347    if (!aOutValues.IsEmpty()) {
    348      aOutValues.Append(HTMLEditUtils::kSpace);
    349    }
    350    aOutValues.AppendLiteral("line-through");
    351  }
    352 }
    353 
    354 }  // namespace mozilla