|
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 "ChangeCSSInlineStyleTxn.h" |
|
7 #include "nsAString.h" // for nsAString_internal::Append, etc |
|
8 #include "nsCRT.h" // for nsCRT |
|
9 #include "nsDebug.h" // for NS_ENSURE_SUCCESS, etc |
|
10 #include "nsError.h" // for NS_ERROR_NULL_POINTER, etc |
|
11 #include "nsGkAtoms.h" // for nsGkAtoms, etc |
|
12 #include "nsIAtom.h" // for nsIAtom |
|
13 #include "nsIDOMCSSStyleDeclaration.h" // for nsIDOMCSSStyleDeclaration |
|
14 #include "nsIDOMElement.h" // for nsIDOMElement |
|
15 #include "nsIDOMElementCSSInlineStyle.h" |
|
16 #include "nsISupportsImpl.h" // for EditTxn::QueryInterface, etc |
|
17 #include "nsISupportsUtils.h" // for NS_ADDREF |
|
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 "nsUnicharUtils.h" |
|
22 #include "nsXPCOM.h" // for NS_Free |
|
23 |
|
24 class nsIEditor; |
|
25 |
|
26 #define kNullCh (char16_t('\0')) |
|
27 |
|
28 NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeCSSInlineStyleTxn, EditTxn, |
|
29 mElement) |
|
30 |
|
31 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeCSSInlineStyleTxn) |
|
32 NS_INTERFACE_MAP_END_INHERITING(EditTxn) |
|
33 |
|
34 // answers true if aValue is in the string list of white-space separated values aValueList |
|
35 // a case-sensitive search is performed if aCaseSensitive is true |
|
36 bool |
|
37 ChangeCSSInlineStyleTxn::ValueIncludes(const nsAString &aValueList, const nsAString &aValue, bool aCaseSensitive) |
|
38 { |
|
39 nsAutoString valueList(aValueList); |
|
40 bool result = false; |
|
41 |
|
42 valueList.Append(kNullCh); // put an extra null at the end |
|
43 |
|
44 char16_t *value = ToNewUnicode(aValue); |
|
45 char16_t *start = valueList.BeginWriting(); |
|
46 char16_t *end = start; |
|
47 |
|
48 while (kNullCh != *start) { |
|
49 while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) { // skip leading space |
|
50 start++; |
|
51 } |
|
52 end = start; |
|
53 |
|
54 while ((kNullCh != *end) && (false == nsCRT::IsAsciiSpace(*end))) { // look for space or end |
|
55 end++; |
|
56 } |
|
57 *end = kNullCh; // end string here |
|
58 |
|
59 if (start < end) { |
|
60 if (aCaseSensitive) { |
|
61 if (!nsCRT::strcmp(value, start)) { |
|
62 result = true; |
|
63 break; |
|
64 } |
|
65 } |
|
66 else { |
|
67 if (nsDependentString(value).Equals(nsDependentString(start), |
|
68 nsCaseInsensitiveStringComparator())) { |
|
69 result = true; |
|
70 break; |
|
71 } |
|
72 } |
|
73 } |
|
74 start = ++end; |
|
75 } |
|
76 NS_Free(value); |
|
77 return result; |
|
78 } |
|
79 |
|
80 // removes the value aRemoveValue from the string list of white-space separated values aValueList |
|
81 void |
|
82 ChangeCSSInlineStyleTxn::RemoveValueFromListOfValues(nsAString & aValues, const nsAString & aRemoveValue) |
|
83 { |
|
84 nsAutoString classStr(aValues); // copy to work buffer nsAutoString rv(aRemoveValue); |
|
85 nsAutoString outString; |
|
86 classStr.Append(kNullCh); // put an extra null at the end |
|
87 |
|
88 char16_t *start = classStr.BeginWriting(); |
|
89 char16_t *end = start; |
|
90 |
|
91 while (kNullCh != *start) { |
|
92 while ((kNullCh != *start) && nsCRT::IsAsciiSpace(*start)) { // skip leading space |
|
93 start++; |
|
94 } |
|
95 end = start; |
|
96 |
|
97 while ((kNullCh != *end) && (false == nsCRT::IsAsciiSpace(*end))) { // look for space or end |
|
98 end++; |
|
99 } |
|
100 *end = kNullCh; // end string here |
|
101 |
|
102 if (start < end) { |
|
103 if (!aRemoveValue.Equals(start)) { |
|
104 outString.Append(start); |
|
105 outString.Append(char16_t(' ')); |
|
106 } |
|
107 } |
|
108 |
|
109 start = ++end; |
|
110 } |
|
111 aValues.Assign(outString); |
|
112 } |
|
113 |
|
114 ChangeCSSInlineStyleTxn::ChangeCSSInlineStyleTxn() |
|
115 : EditTxn() |
|
116 { |
|
117 } |
|
118 |
|
119 NS_IMETHODIMP ChangeCSSInlineStyleTxn::Init(nsIEditor *aEditor, |
|
120 nsIDOMElement *aElement, |
|
121 nsIAtom *aProperty, |
|
122 const nsAString& aValue, |
|
123 bool aRemoveProperty) |
|
124 { |
|
125 NS_ASSERTION(aEditor && aElement, "bad arg"); |
|
126 if (!aEditor || !aElement) { return NS_ERROR_NULL_POINTER; } |
|
127 |
|
128 mEditor = aEditor; |
|
129 mElement = do_QueryInterface(aElement); |
|
130 mProperty = aProperty; |
|
131 NS_ADDREF(mProperty); |
|
132 mValue.Assign(aValue); |
|
133 mRemoveProperty = aRemoveProperty; |
|
134 mUndoAttributeWasSet = false; |
|
135 mRedoAttributeWasSet = false; |
|
136 mUndoValue.Truncate(); |
|
137 mRedoValue.Truncate(); |
|
138 return NS_OK; |
|
139 } |
|
140 |
|
141 NS_IMETHODIMP ChangeCSSInlineStyleTxn::DoTransaction(void) |
|
142 { |
|
143 NS_ASSERTION(mEditor && mElement, "bad state"); |
|
144 if (!mEditor || !mElement) { return NS_ERROR_NOT_INITIALIZED; } |
|
145 |
|
146 nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyles = do_QueryInterface(mElement); |
|
147 NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); |
|
148 |
|
149 nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl; |
|
150 nsresult result = inlineStyles->GetStyle(getter_AddRefs(cssDecl)); |
|
151 NS_ENSURE_SUCCESS(result, result); |
|
152 NS_ENSURE_TRUE(cssDecl, NS_ERROR_NULL_POINTER); |
|
153 |
|
154 nsAutoString propertyNameString; |
|
155 mProperty->ToString(propertyNameString); |
|
156 |
|
157 NS_NAMED_LITERAL_STRING(styleAttr, "style"); |
|
158 result = mElement->HasAttribute(styleAttr, &mUndoAttributeWasSet); |
|
159 NS_ENSURE_SUCCESS(result, result); |
|
160 |
|
161 nsAutoString values; |
|
162 result = cssDecl->GetPropertyValue(propertyNameString, values); |
|
163 NS_ENSURE_SUCCESS(result, result); |
|
164 mUndoValue.Assign(values); |
|
165 |
|
166 // does this property accept more than 1 value ? |
|
167 // we need to know that because of bug 62682 |
|
168 bool multiple = AcceptsMoreThanOneValue(mProperty); |
|
169 |
|
170 if (mRemoveProperty) { |
|
171 nsAutoString returnString; |
|
172 if (multiple) { |
|
173 // the property can have more than one value, let's remove only |
|
174 // the value we have to remove and not the others |
|
175 |
|
176 // the 2 lines below are a workaround because nsDOMCSSDeclaration::GetPropertyCSSValue |
|
177 // is not yet implemented (bug 62682) |
|
178 RemoveValueFromListOfValues(values, NS_LITERAL_STRING("none")); |
|
179 RemoveValueFromListOfValues(values, mValue); |
|
180 if (values.IsEmpty()) { |
|
181 result = cssDecl->RemoveProperty(propertyNameString, returnString); |
|
182 NS_ENSURE_SUCCESS(result, result); |
|
183 } |
|
184 else { |
|
185 nsAutoString priority; |
|
186 result = cssDecl->GetPropertyPriority(propertyNameString, priority); |
|
187 NS_ENSURE_SUCCESS(result, result); |
|
188 result = cssDecl->SetProperty(propertyNameString, values, |
|
189 priority); |
|
190 NS_ENSURE_SUCCESS(result, result); |
|
191 } |
|
192 } |
|
193 else { |
|
194 result = cssDecl->RemoveProperty(propertyNameString, returnString); |
|
195 NS_ENSURE_SUCCESS(result, result); |
|
196 } |
|
197 } |
|
198 else { |
|
199 nsAutoString priority; |
|
200 result = cssDecl->GetPropertyPriority(propertyNameString, priority); |
|
201 NS_ENSURE_SUCCESS(result, result); |
|
202 if (multiple) { |
|
203 // the property can have more than one value, let's add |
|
204 // the value we have to add to the others |
|
205 |
|
206 // the line below is a workaround because nsDOMCSSDeclaration::GetPropertyCSSValue |
|
207 // is not yet implemented (bug 62682) |
|
208 AddValueToMultivalueProperty(values, mValue); |
|
209 } |
|
210 else |
|
211 values.Assign(mValue); |
|
212 result = cssDecl->SetProperty(propertyNameString, values, |
|
213 priority); |
|
214 NS_ENSURE_SUCCESS(result, result); |
|
215 } |
|
216 |
|
217 // let's be sure we don't keep an empty style attribute |
|
218 uint32_t length; |
|
219 result = cssDecl->GetLength(&length); |
|
220 NS_ENSURE_SUCCESS(result, result); |
|
221 if (!length) { |
|
222 result = mElement->RemoveAttribute(styleAttr); |
|
223 NS_ENSURE_SUCCESS(result, result); |
|
224 } |
|
225 else |
|
226 mRedoAttributeWasSet = true; |
|
227 |
|
228 return cssDecl->GetPropertyValue(propertyNameString, mRedoValue); |
|
229 } |
|
230 |
|
231 nsresult ChangeCSSInlineStyleTxn::SetStyle(bool aAttributeWasSet, |
|
232 nsAString & aValue) |
|
233 { |
|
234 NS_ASSERTION(mEditor && mElement, "bad state"); |
|
235 if (!mEditor || !mElement) { return NS_ERROR_NOT_INITIALIZED; } |
|
236 |
|
237 nsresult result = NS_OK; |
|
238 if (aAttributeWasSet) { |
|
239 // the style attribute was set and not empty, let's recreate the declaration |
|
240 nsAutoString propertyNameString; |
|
241 mProperty->ToString(propertyNameString); |
|
242 |
|
243 nsCOMPtr<nsIDOMElementCSSInlineStyle> inlineStyles = do_QueryInterface(mElement); |
|
244 NS_ENSURE_TRUE(inlineStyles, NS_ERROR_NULL_POINTER); |
|
245 nsCOMPtr<nsIDOMCSSStyleDeclaration> cssDecl; |
|
246 result = inlineStyles->GetStyle(getter_AddRefs(cssDecl)); |
|
247 NS_ENSURE_SUCCESS(result, result); |
|
248 NS_ENSURE_TRUE(cssDecl, NS_ERROR_NULL_POINTER); |
|
249 |
|
250 if (aValue.IsEmpty()) { |
|
251 // an empty value means we have to remove the property |
|
252 nsAutoString returnString; |
|
253 result = cssDecl->RemoveProperty(propertyNameString, returnString); |
|
254 } |
|
255 else { |
|
256 // let's recreate the declaration as it was |
|
257 nsAutoString priority; |
|
258 result = cssDecl->GetPropertyPriority(propertyNameString, priority); |
|
259 NS_ENSURE_SUCCESS(result, result); |
|
260 result = cssDecl->SetProperty(propertyNameString, aValue, priority); |
|
261 } |
|
262 } |
|
263 else |
|
264 result = mElement->RemoveAttribute(NS_LITERAL_STRING("style")); |
|
265 |
|
266 return result; |
|
267 } |
|
268 |
|
269 NS_IMETHODIMP ChangeCSSInlineStyleTxn::UndoTransaction(void) |
|
270 { |
|
271 return SetStyle(mUndoAttributeWasSet, mUndoValue); |
|
272 } |
|
273 |
|
274 NS_IMETHODIMP ChangeCSSInlineStyleTxn::RedoTransaction(void) |
|
275 { |
|
276 return SetStyle(mRedoAttributeWasSet, mRedoValue); |
|
277 } |
|
278 |
|
279 NS_IMETHODIMP ChangeCSSInlineStyleTxn::GetTxnDescription(nsAString& aString) |
|
280 { |
|
281 aString.AssignLiteral("ChangeCSSInlineStyleTxn: [mRemoveProperty == "); |
|
282 |
|
283 if (!mRemoveProperty) |
|
284 aString.AppendLiteral("false] "); |
|
285 else |
|
286 aString.AppendLiteral("true] "); |
|
287 nsAutoString tempString; |
|
288 mProperty->ToString(tempString); |
|
289 aString += tempString; |
|
290 return NS_OK; |
|
291 } |
|
292 |
|
293 // answers true if the CSS property accepts more than one value |
|
294 bool |
|
295 ChangeCSSInlineStyleTxn::AcceptsMoreThanOneValue(nsIAtom *aCSSProperty) |
|
296 { |
|
297 return aCSSProperty == nsGkAtoms::text_decoration; |
|
298 } |
|
299 |
|
300 // adds the value aNewValue to the list of white-space separated values aValues |
|
301 NS_IMETHODIMP |
|
302 ChangeCSSInlineStyleTxn::AddValueToMultivalueProperty(nsAString & aValues, const nsAString & aNewValue) |
|
303 { |
|
304 if (aValues.IsEmpty() |
|
305 || aValues.LowerCaseEqualsLiteral("none")) { |
|
306 // the list of values is empty of the value is 'none' |
|
307 aValues.Assign(aNewValue); |
|
308 } |
|
309 else if (!ValueIncludes(aValues, aNewValue, false)) { |
|
310 // we already have another value but not this one; add it |
|
311 aValues.Append(char16_t(' ')); |
|
312 aValues.Append(aNewValue); |
|
313 } |
|
314 return NS_OK; |
|
315 } |