diff options
Diffstat (limited to '')
-rw-r--r-- | editor/libeditor/ChangeStyleTransaction.cpp | 311 |
1 files changed, 311 insertions, 0 deletions
diff --git a/editor/libeditor/ChangeStyleTransaction.cpp b/editor/libeditor/ChangeStyleTransaction.cpp new file mode 100644 index 0000000000..2215066802 --- /dev/null +++ b/editor/libeditor/ChangeStyleTransaction.cpp @@ -0,0 +1,311 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/ChangeStyleTransaction.h" + +#include "mozilla/dom/Element.h" // for Element +#include "nsAString.h" // for nsAString::Append, etc. +#include "nsCRT.h" // for nsCRT::IsAsciiSpace +#include "nsDebug.h" // for NS_WARNING, etc. +#include "nsError.h" // for NS_ERROR_NULL_POINTER, etc. +#include "nsGkAtoms.h" // for nsGkAtoms, etc. +#include "nsICSSDeclaration.h" // for nsICSSDeclaration. +#include "nsLiteralString.h" // for NS_LITERAL_STRING, etc. +#include "nsReadableUtils.h" // for ToNewUnicode +#include "nsString.h" // for nsAutoString, nsString, etc. +#include "nsStyledElement.h" // for nsStyledElement. +#include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator + +namespace mozilla { + +using namespace dom; + +// static +already_AddRefed<ChangeStyleTransaction> ChangeStyleTransaction::Create( + nsStyledElement& aStyledElement, nsAtom& aProperty, + const nsAString& aValue) { + RefPtr<ChangeStyleTransaction> transaction = + new ChangeStyleTransaction(aStyledElement, aProperty, aValue, false); + return transaction.forget(); +} + +// static +already_AddRefed<ChangeStyleTransaction> ChangeStyleTransaction::CreateToRemove( + nsStyledElement& aStyledElement, nsAtom& aProperty, + const nsAString& aValue) { + RefPtr<ChangeStyleTransaction> transaction = + new ChangeStyleTransaction(aStyledElement, aProperty, aValue, true); + return transaction.forget(); +} + +ChangeStyleTransaction::ChangeStyleTransaction(nsStyledElement& aStyledElement, + nsAtom& aProperty, + const nsAString& aValue, + bool aRemove) + : EditTransactionBase(), + mStyledElement(&aStyledElement), + mProperty(&aProperty), + mUndoValue(), + mRedoValue(), + mRemoveProperty(aRemove), + mUndoAttributeWasSet(false), + mRedoAttributeWasSet(false) { + CopyUTF16toUTF8(aValue, mValue); +} + +#define kNullCh ('\0') + +NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase, + mStyledElement) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction) +NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase) + +NS_IMPL_ADDREF_INHERITED(ChangeStyleTransaction, EditTransactionBase) +NS_IMPL_RELEASE_INHERITED(ChangeStyleTransaction, EditTransactionBase) + +// Answers true if aValue is in the string list of white-space separated values +// aValueList. +bool ChangeStyleTransaction::ValueIncludes(const nsACString& aValueList, + const nsACString& aValue) { + nsAutoCString valueList(aValueList); + bool result = false; + + // put an extra null at the end + valueList.Append(kNullCh); + + char* start = valueList.BeginWriting(); + char* end = start; + + while (kNullCh != *start) { + while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) { + // skip leading space + start++; + } + end = start; + + while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) { + // look for space or end + end++; + } + // end string here + *end = kNullCh; + + if (start < end) { + if (aValue.Equals(nsDependentCString(start), + nsCaseInsensitiveCStringComparator)) { + result = true; + break; + } + } + start = ++end; + } + return result; +} + +// Removes the value aRemoveValue from the string list of white-space separated +// values aValueList +void ChangeStyleTransaction::RemoveValueFromListOfValues( + nsACString& aValues, const nsACString& aRemoveValue) { + nsAutoCString classStr(aValues); + nsAutoCString outString; + // put an extra null at the end + classStr.Append(kNullCh); + + char* start = classStr.BeginWriting(); + char* end = start; + + while (kNullCh != *start) { + while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) { + // skip leading space + start++; + } + end = start; + + while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) { + // look for space or end + end++; + } + // end string here + *end = kNullCh; + + if (start < end && !aRemoveValue.Equals(start)) { + outString.Append(start); + outString.Append(' '); + } + + start = ++end; + } + aValues.Assign(outString); +} + +NS_IMETHODIMP ChangeStyleTransaction::DoTransaction() { + if (NS_WARN_IF(!mStyledElement)) { + return NS_ERROR_NOT_AVAILABLE; + } + + OwningNonNull<nsStyledElement> styledElement = *mStyledElement; + nsCOMPtr<nsICSSDeclaration> cssDecl = styledElement->Style(); + + // FIXME(bug 1606994): Using atoms forces a string copy here which is not + // great. + nsAutoCString propertyNameString; + mProperty->ToUTF8String(propertyNameString); + + mUndoAttributeWasSet = + mStyledElement->HasAttr(kNameSpaceID_None, nsGkAtoms::style); + + nsAutoCString values; + nsresult rv = cssDecl->GetPropertyValue(propertyNameString, values); + if (NS_FAILED(rv)) { + NS_WARNING("nsICSSDeclaration::GetPropertyPriorityValue() failed"); + return rv; + } + mUndoValue.Assign(values); + + // Does this property accept more than one value? (bug 62682) + bool multiple = AcceptsMoreThanOneValue(*mProperty); + + if (mRemoveProperty) { + nsAutoCString returnString; + if (multiple) { + // Let's remove only the value we have to remove and not the others + RemoveValueFromListOfValues(values, "none"_ns); + RemoveValueFromListOfValues(values, mValue); + if (values.IsEmpty()) { + ErrorResult error; + cssDecl->RemoveProperty(propertyNameString, returnString, error); + if (error.Failed()) { + NS_WARNING("nsICSSDeclaration::RemoveProperty() failed"); + return error.StealNSResult(); + } + } else { + ErrorResult error; + nsAutoCString priority; + cssDecl->GetPropertyPriority(propertyNameString, priority); + cssDecl->SetProperty(propertyNameString, values, priority, error); + if (error.Failed()) { + NS_WARNING("nsICSSDeclaration::SetProperty() failed"); + return error.StealNSResult(); + } + } + } else { + ErrorResult error; + cssDecl->RemoveProperty(propertyNameString, returnString, error); + if (error.Failed()) { + NS_WARNING("nsICSSDeclaration::RemoveProperty() failed"); + return error.StealNSResult(); + } + } + } else { + nsAutoCString priority; + cssDecl->GetPropertyPriority(propertyNameString, priority); + if (multiple) { + // Let's add the value we have to add to the others + AddValueToMultivalueProperty(values, mValue); + } else { + values.Assign(mValue); + } + ErrorResult error; + cssDecl->SetProperty(propertyNameString, values, priority, error); + if (error.Failed()) { + NS_WARNING("nsICSSDeclaration::SetProperty() failed"); + return error.StealNSResult(); + } + } + + // Let's be sure we don't keep an empty style attribute + uint32_t length = cssDecl->Length(); + if (!length) { + nsresult rv = + styledElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true); + if (NS_FAILED(rv)) { + NS_WARNING("Element::UnsetAttr(nsGkAtoms::style) failed"); + return rv; + } + } else { + mRedoAttributeWasSet = true; + } + + rv = cssDecl->GetPropertyValue(propertyNameString, mRedoValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "nsICSSDeclaration::GetPropertyValue() failed"); + return rv; +} + +nsresult ChangeStyleTransaction::SetStyle(bool aAttributeWasSet, + nsACString& aValue) { + if (NS_WARN_IF(!mStyledElement)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (aAttributeWasSet) { + OwningNonNull<nsStyledElement> styledElement = *mStyledElement; + + // The style attribute was not empty, let's recreate the declaration + nsAutoCString propertyNameString; + mProperty->ToUTF8String(propertyNameString); + + nsCOMPtr<nsICSSDeclaration> cssDecl = styledElement->Style(); + + ErrorResult error; + if (aValue.IsEmpty()) { + // An empty value means we have to remove the property + nsAutoCString returnString; + cssDecl->RemoveProperty(propertyNameString, returnString, error); + if (error.Failed()) { + NS_WARNING("nsICSSDeclaration::RemoveProperty() failed"); + return error.StealNSResult(); + } + } + // Let's recreate the declaration as it was + nsAutoCString priority; + cssDecl->GetPropertyPriority(propertyNameString, priority); + cssDecl->SetProperty(propertyNameString, aValue, priority, error); + NS_WARNING_ASSERTION(!error.Failed(), + "nsICSSDeclaration::SetProperty() failed"); + return error.StealNSResult(); + } + + OwningNonNull<nsStyledElement> styledElement = *mStyledElement; + nsresult rv = + styledElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "Element::UnsetAttr(nsGkAtoms::style) failed"); + return rv; +} + +NS_IMETHODIMP ChangeStyleTransaction::UndoTransaction() { + nsresult rv = SetStyle(mUndoAttributeWasSet, mUndoValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "ChangeStyleTransaction::SetStyle() failed"); + return rv; +} + +NS_IMETHODIMP ChangeStyleTransaction::RedoTransaction() { + nsresult rv = SetStyle(mRedoAttributeWasSet, mRedoValue); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), + "ChangeStyleTransaction::SetStyle() failed"); + return rv; +} + +// True if the CSS property accepts more than one value +bool ChangeStyleTransaction::AcceptsMoreThanOneValue(nsAtom& aCSSProperty) { + return &aCSSProperty == nsGkAtoms::text_decoration; +} + +// Adds the value aNewValue to the list of white-space separated values aValues +void ChangeStyleTransaction::AddValueToMultivalueProperty( + nsACString& aValues, const nsACString& aNewValue) { + if (aValues.IsEmpty() || aValues.LowerCaseEqualsLiteral("none")) { + aValues.Assign(aNewValue); + } else if (!ValueIncludes(aValues, aNewValue)) { + // We already have another value but not this one; add it + aValues.Append(char16_t(' ')); + aValues.Append(aNewValue); + } +} + +} // namespace mozilla |