Bug 1772588 [wpt PR 34302] - [wpt] Add test for block-in-inline offsetParent., a...
[gecko.git] / editor / libeditor / ChangeStyleTransaction.cpp
blobc88591cbddf9e2aadc502fd1acc7aaebfd923d2e
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/. */
6 #include "ChangeStyleTransaction.h"
8 #include "HTMLEditUtils.h"
9 #include "mozilla/Logging.h"
10 #include "mozilla/ToString.h"
11 #include "mozilla/dom/Element.h" // for Element
12 #include "nsAString.h" // for nsAString::Append, etc.
13 #include "nsCRT.h" // for nsCRT::IsAsciiSpace
14 #include "nsDebug.h" // for NS_WARNING, etc.
15 #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc.
16 #include "nsGkAtoms.h" // for nsGkAtoms, etc.
17 #include "nsICSSDeclaration.h" // for nsICSSDeclaration.
18 #include "nsLiteralString.h" // for NS_LITERAL_STRING, etc.
19 #include "nsReadableUtils.h" // for ToNewUnicode
20 #include "nsString.h" // for nsAutoString, nsString, etc.
21 #include "nsStyledElement.h" // for nsStyledElement.
22 #include "nsUnicharUtils.h" // for nsCaseInsensitiveStringComparator
24 namespace mozilla {
26 using namespace dom;
28 // static
29 already_AddRefed<ChangeStyleTransaction> ChangeStyleTransaction::Create(
30 nsStyledElement& aStyledElement, nsAtom& aProperty,
31 const nsAString& aValue) {
32 RefPtr<ChangeStyleTransaction> transaction =
33 new ChangeStyleTransaction(aStyledElement, aProperty, aValue, false);
34 return transaction.forget();
37 // static
38 already_AddRefed<ChangeStyleTransaction> ChangeStyleTransaction::CreateToRemove(
39 nsStyledElement& aStyledElement, nsAtom& aProperty,
40 const nsAString& aValue) {
41 RefPtr<ChangeStyleTransaction> transaction =
42 new ChangeStyleTransaction(aStyledElement, aProperty, aValue, true);
43 return transaction.forget();
46 ChangeStyleTransaction::ChangeStyleTransaction(nsStyledElement& aStyledElement,
47 nsAtom& aProperty,
48 const nsAString& aValue,
49 bool aRemove)
50 : EditTransactionBase(),
51 mStyledElement(&aStyledElement),
52 mProperty(&aProperty),
53 mUndoValue(),
54 mRedoValue(),
55 mRemoveProperty(aRemove),
56 mUndoAttributeWasSet(false),
57 mRedoAttributeWasSet(false) {
58 CopyUTF16toUTF8(aValue, mValue);
61 std::ostream& operator<<(std::ostream& aStream,
62 const ChangeStyleTransaction& aTransaction) {
63 aStream << "{ mStyledElement=" << aTransaction.mStyledElement.get();
64 if (aTransaction.mStyledElement) {
65 aStream << " (" << *aTransaction.mStyledElement << ")";
67 aStream << ", mProperty=" << nsAtomCString(aTransaction.mProperty).get()
68 << ", mValue=\"" << aTransaction.mValue.get() << "\", mUndoValue=\""
69 << aTransaction.mUndoValue.get()
70 << "\", mRedoValue=" << aTransaction.mRedoValue.get()
71 << ", mRemoveProperty="
72 << (aTransaction.mRemoveProperty ? "true" : "false")
73 << ", mUndoAttributeWasSet="
74 << (aTransaction.mUndoAttributeWasSet ? "true" : "false")
75 << ", mRedoAttributeWasSet="
76 << (aTransaction.mRedoAttributeWasSet ? "true" : "false") << " }";
77 return aStream;
80 #define kNullCh ('\0')
82 NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase,
83 mStyledElement)
85 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction)
86 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
88 NS_IMPL_ADDREF_INHERITED(ChangeStyleTransaction, EditTransactionBase)
89 NS_IMPL_RELEASE_INHERITED(ChangeStyleTransaction, EditTransactionBase)
91 // Answers true if aValue is in the string list of white-space separated values
92 // aValueList.
93 bool ChangeStyleTransaction::ValueIncludes(const nsACString& aValueList,
94 const nsACString& aValue) {
95 nsAutoCString valueList(aValueList);
96 bool result = false;
98 // put an extra null at the end
99 valueList.Append(kNullCh);
101 char* start = valueList.BeginWriting();
102 char* end = start;
104 while (kNullCh != *start) {
105 while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
106 // skip leading space
107 start++;
109 end = start;
111 while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
112 // look for space or end
113 end++;
115 // end string here
116 *end = kNullCh;
118 if (start < end) {
119 if (aValue.Equals(nsDependentCString(start),
120 nsCaseInsensitiveCStringComparator)) {
121 result = true;
122 break;
125 start = ++end;
127 return result;
130 // Removes the value aRemoveValue from the string list of white-space separated
131 // values aValueList
132 void ChangeStyleTransaction::RemoveValueFromListOfValues(
133 nsACString& aValues, const nsACString& aRemoveValue) {
134 nsAutoCString classStr(aValues);
135 nsAutoCString outString;
136 // put an extra null at the end
137 classStr.Append(kNullCh);
139 char* start = classStr.BeginWriting();
140 char* end = start;
142 while (kNullCh != *start) {
143 while (kNullCh != *start && nsCRT::IsAsciiSpace(*start)) {
144 // skip leading space
145 start++;
147 end = start;
149 while (kNullCh != *end && !nsCRT::IsAsciiSpace(*end)) {
150 // look for space or end
151 end++;
153 // end string here
154 *end = kNullCh;
156 if (start < end && !aRemoveValue.Equals(start)) {
157 outString.Append(start);
158 outString.Append(HTMLEditUtils::kSpace);
161 start = ++end;
163 aValues.Assign(outString);
166 NS_IMETHODIMP ChangeStyleTransaction::DoTransaction() {
167 MOZ_LOG(GetLogModule(), LogLevel::Info,
168 ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
169 ToString(*this).c_str()));
171 if (NS_WARN_IF(!mStyledElement)) {
172 return NS_ERROR_NOT_AVAILABLE;
175 OwningNonNull<nsStyledElement> styledElement = *mStyledElement;
176 nsCOMPtr<nsICSSDeclaration> cssDecl = styledElement->Style();
178 // FIXME(bug 1606994): Using atoms forces a string copy here which is not
179 // great.
180 nsAutoCString propertyNameString;
181 mProperty->ToUTF8String(propertyNameString);
183 mUndoAttributeWasSet =
184 mStyledElement->HasAttr(kNameSpaceID_None, nsGkAtoms::style);
186 nsAutoCString values;
187 nsresult rv = cssDecl->GetPropertyValue(propertyNameString, values);
188 if (NS_FAILED(rv)) {
189 NS_WARNING("nsICSSDeclaration::GetPropertyPriorityValue() failed");
190 return rv;
192 mUndoValue.Assign(values);
194 // Does this property accept more than one value? (bug 62682)
195 bool multiple = AcceptsMoreThanOneValue(*mProperty);
197 if (mRemoveProperty) {
198 nsAutoCString returnString;
199 if (multiple) {
200 // Let's remove only the value we have to remove and not the others
201 RemoveValueFromListOfValues(values, "none"_ns);
202 RemoveValueFromListOfValues(values, mValue);
203 if (values.IsEmpty()) {
204 ErrorResult error;
205 cssDecl->RemoveProperty(propertyNameString, returnString, error);
206 if (error.Failed()) {
207 NS_WARNING("nsICSSDeclaration::RemoveProperty() failed");
208 return error.StealNSResult();
210 } else {
211 ErrorResult error;
212 nsAutoCString priority;
213 cssDecl->GetPropertyPriority(propertyNameString, priority);
214 cssDecl->SetProperty(propertyNameString, values, priority, error);
215 if (error.Failed()) {
216 NS_WARNING("nsICSSDeclaration::SetProperty() failed");
217 return error.StealNSResult();
220 } else {
221 ErrorResult error;
222 cssDecl->RemoveProperty(propertyNameString, returnString, error);
223 if (error.Failed()) {
224 NS_WARNING("nsICSSDeclaration::RemoveProperty() failed");
225 return error.StealNSResult();
228 } else {
229 nsAutoCString priority;
230 cssDecl->GetPropertyPriority(propertyNameString, priority);
231 if (multiple) {
232 // Let's add the value we have to add to the others
233 AddValueToMultivalueProperty(values, mValue);
234 } else {
235 values.Assign(mValue);
237 ErrorResult error;
238 cssDecl->SetProperty(propertyNameString, values, priority, error);
239 if (error.Failed()) {
240 NS_WARNING("nsICSSDeclaration::SetProperty() failed");
241 return error.StealNSResult();
245 // Let's be sure we don't keep an empty style attribute
246 uint32_t length = cssDecl->Length();
247 if (!length) {
248 nsresult rv =
249 styledElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
250 if (NS_FAILED(rv)) {
251 NS_WARNING("Element::UnsetAttr(nsGkAtoms::style) failed");
252 return rv;
254 } else {
255 mRedoAttributeWasSet = true;
258 rv = cssDecl->GetPropertyValue(propertyNameString, mRedoValue);
259 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
260 "nsICSSDeclaration::GetPropertyValue() failed");
261 return rv;
264 nsresult ChangeStyleTransaction::SetStyle(bool aAttributeWasSet,
265 nsACString& aValue) {
266 if (NS_WARN_IF(!mStyledElement)) {
267 return NS_ERROR_NOT_AVAILABLE;
270 if (aAttributeWasSet) {
271 OwningNonNull<nsStyledElement> styledElement = *mStyledElement;
273 // The style attribute was not empty, let's recreate the declaration
274 nsAutoCString propertyNameString;
275 mProperty->ToUTF8String(propertyNameString);
277 nsCOMPtr<nsICSSDeclaration> cssDecl = styledElement->Style();
279 ErrorResult error;
280 if (aValue.IsEmpty()) {
281 // An empty value means we have to remove the property
282 nsAutoCString returnString;
283 cssDecl->RemoveProperty(propertyNameString, returnString, error);
284 if (error.Failed()) {
285 NS_WARNING("nsICSSDeclaration::RemoveProperty() failed");
286 return error.StealNSResult();
289 // Let's recreate the declaration as it was
290 nsAutoCString priority;
291 cssDecl->GetPropertyPriority(propertyNameString, priority);
292 cssDecl->SetProperty(propertyNameString, aValue, priority, error);
293 NS_WARNING_ASSERTION(!error.Failed(),
294 "nsICSSDeclaration::SetProperty() failed");
295 return error.StealNSResult();
298 OwningNonNull<nsStyledElement> styledElement = *mStyledElement;
299 nsresult rv =
300 styledElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
301 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
302 "Element::UnsetAttr(nsGkAtoms::style) failed");
303 return rv;
306 NS_IMETHODIMP ChangeStyleTransaction::UndoTransaction() {
307 MOZ_LOG(GetLogModule(), LogLevel::Info,
308 ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
309 ToString(*this).c_str()));
311 nsresult rv = SetStyle(mUndoAttributeWasSet, mUndoValue);
312 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
313 "ChangeStyleTransaction::SetStyle() failed");
314 return rv;
317 NS_IMETHODIMP ChangeStyleTransaction::RedoTransaction() {
318 MOZ_LOG(GetLogModule(), LogLevel::Info,
319 ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
320 ToString(*this).c_str()));
322 nsresult rv = SetStyle(mRedoAttributeWasSet, mRedoValue);
323 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
324 "ChangeStyleTransaction::SetStyle() failed");
325 return rv;
328 // True if the CSS property accepts more than one value
329 bool ChangeStyleTransaction::AcceptsMoreThanOneValue(nsAtom& aCSSProperty) {
330 return &aCSSProperty == nsGkAtoms::text_decoration;
333 // Adds the value aNewValue to the list of white-space separated values aValues
334 void ChangeStyleTransaction::AddValueToMultivalueProperty(
335 nsACString& aValues, const nsACString& aNewValue) {
336 if (aValues.IsEmpty() || aValues.LowerCaseEqualsLiteral("none")) {
337 aValues.Assign(aNewValue);
338 } else if (!ValueIncludes(aValues, aNewValue)) {
339 // We already have another value but not this one; add it
340 aValues.Append(HTMLEditUtils::kSpace);
341 aValues.Append(aNewValue);
345 } // namespace mozilla