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
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();
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
,
48 const nsAString
& aValue
,
50 : EditTransactionBase(),
51 mStyledElement(&aStyledElement
),
52 mProperty(&aProperty
),
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") << " }";
80 #define kNullCh ('\0')
82 NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction
, EditTransactionBase
,
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
93 bool ChangeStyleTransaction::ValueIncludes(const nsACString
& aValueList
,
94 const nsACString
& aValue
) {
95 nsAutoCString
valueList(aValueList
);
98 // put an extra null at the end
99 valueList
.Append(kNullCh
);
101 char* start
= valueList
.BeginWriting();
104 while (kNullCh
!= *start
) {
105 while (kNullCh
!= *start
&& nsCRT::IsAsciiSpace(*start
)) {
106 // skip leading space
111 while (kNullCh
!= *end
&& !nsCRT::IsAsciiSpace(*end
)) {
112 // look for space or end
119 if (aValue
.Equals(nsDependentCString(start
),
120 nsCaseInsensitiveCStringComparator
)) {
130 // Removes the value aRemoveValue from the string list of white-space separated
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();
142 while (kNullCh
!= *start
) {
143 while (kNullCh
!= *start
&& nsCRT::IsAsciiSpace(*start
)) {
144 // skip leading space
149 while (kNullCh
!= *end
&& !nsCRT::IsAsciiSpace(*end
)) {
150 // look for space or end
156 if (start
< end
&& !aRemoveValue
.Equals(start
)) {
157 outString
.Append(start
);
158 outString
.Append(HTMLEditUtils::kSpace
);
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
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
);
189 NS_WARNING("nsICSSDeclaration::GetPropertyPriorityValue() failed");
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
;
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()) {
205 cssDecl
->RemoveProperty(propertyNameString
, returnString
, error
);
206 if (error
.Failed()) {
207 NS_WARNING("nsICSSDeclaration::RemoveProperty() failed");
208 return error
.StealNSResult();
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();
222 cssDecl
->RemoveProperty(propertyNameString
, returnString
, error
);
223 if (error
.Failed()) {
224 NS_WARNING("nsICSSDeclaration::RemoveProperty() failed");
225 return error
.StealNSResult();
229 nsAutoCString priority
;
230 cssDecl
->GetPropertyPriority(propertyNameString
, priority
);
232 // Let's add the value we have to add to the others
233 AddValueToMultivalueProperty(values
, mValue
);
235 values
.Assign(mValue
);
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();
249 styledElement
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::style
, true);
251 NS_WARNING("Element::UnsetAttr(nsGkAtoms::style) failed");
255 mRedoAttributeWasSet
= true;
258 rv
= cssDecl
->GetPropertyValue(propertyNameString
, mRedoValue
);
259 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
260 "nsICSSDeclaration::GetPropertyValue() failed");
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();
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
;
300 styledElement
->UnsetAttr(kNameSpaceID_None
, nsGkAtoms::style
, true);
301 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
302 "Element::UnsetAttr(nsGkAtoms::style) failed");
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");
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");
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