summaryrefslogtreecommitdiffstats
path: root/editor/libeditor/ChangeStyleTransaction.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--editor/libeditor/ChangeStyleTransaction.cpp311
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